Merge "Add uninstallReason to Settings dump"
diff --git a/ApiDocs.bp b/ApiDocs.bp
index c50d446..da6a2f6 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -55,25 +55,9 @@
"android-support-multidex-instrumentation",
]
+// These defaults enable doc-stub generation, api lint database generation and sdk value generation.
stubs_defaults {
name: "android-non-updatable-doc-stubs-defaults",
- defaults: [
- "android-non-updatable-stubs-defaults",
- "module-classpath-stubs-defaults",
- ],
- srcs: [
- // No longer part of the stubs, but are included in the docs.
- ":android-test-base-sources",
- ":android-test-mock-sources",
- ":android-test-runner-sources",
- ],
- libs: framework_docs_only_libs,
- create_doc_stubs: true,
- write_sdk_values: true,
-}
-
-stubs_defaults {
- name: "framework-doc-stubs-default",
defaults: ["android-non-updatable-stubs-defaults"],
srcs: [
// No longer part of the stubs, but are included in the docs.
@@ -83,18 +67,13 @@
],
libs: framework_docs_only_libs,
create_doc_stubs: true,
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
write_sdk_values: true,
}
// Defaults module for doc-stubs targets that use module source code as input.
stubs_defaults {
name: "framework-doc-stubs-sources-default",
- defaults: ["framework-doc-stubs-default"],
+ defaults: ["android-non-updatable-doc-stubs-defaults"],
srcs: [
":art.module.public.api{.public.stubs.source}",
":conscrypt.module.public.api{.public.stubs.source}",
@@ -123,13 +102,19 @@
droidstubs {
name: "android-non-updatable-doc-stubs",
- defaults: ["android-non-updatable-doc-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-doc-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args,
}
droidstubs {
name: "android-non-updatable-doc-stubs-system",
- defaults: ["android-non-updatable-doc-stubs-defaults"],
+ defaults: [
+ "android-non-updatable-doc-stubs-defaults",
+ "module-classpath-stubs-defaults",
+ ],
args: metalava_framework_docs_args +
" --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
}
@@ -139,14 +124,24 @@
defaults: ["framework-doc-stubs-sources-default"],
args: metalava_framework_docs_args +
" --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
api_levels_sdk_type: "system",
}
droidstubs {
name: "framework-doc-stubs",
- defaults: ["framework-doc-stubs-default"],
+ defaults: ["android-non-updatable-doc-stubs-defaults"],
srcs: [":all-modules-public-stubs-source"],
args: metalava_framework_docs_args,
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
aidl: {
local_include_dirs: [
"apex/media/aidl/stable",
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 9fb1227..afad29c 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -259,10 +259,9 @@
*/
public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
/**
- * Broadcast {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}.
- * @hide
+ * Broadcast {@link android.safetycenter.SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}.
*/
- public static final int REASON_ACTION_REFRESH_SAFETY_SOURCES = 208;
+ public static final int REASON_REFRESH_SAFETY_SOURCES = 208;
/* Reason code range 300-399 are reserved for other internal reasons */
/**
@@ -350,6 +349,21 @@
* @hide
*/
public static final int REASON_MEDIA_SESSION_CALLBACK = 317;
+ /**
+ * Dialer app.
+ * @hide
+ */
+ public static final int REASON_ROLE_DIALER = 318;
+ /**
+ * Emergency app.
+ * @hide
+ */
+ public static final int REASON_ROLE_EMERGENCY = 319;
+ /**
+ * System Module.
+ * @hide
+ */
+ public static final int REASON_SYSTEM_MODULE = 320;
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -404,7 +418,7 @@
REASON_TIME_CHANGED,
REASON_LOCALE_CHANGED,
REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
- REASON_ACTION_REFRESH_SAFETY_SOURCES,
+ REASON_REFRESH_SAFETY_SOURCES,
REASON_SYSTEM_ALLOW_LISTED,
REASON_ALARM_MANAGER_ALARM_CLOCK,
REASON_ALARM_MANAGER_WHILE_IDLE,
@@ -423,6 +437,9 @@
REASON_EVENT_MMS,
REASON_SHELL,
REASON_MEDIA_SESSION_CALLBACK,
+ REASON_ROLE_DIALER,
+ REASON_ROLE_EMERGENCY,
+ REASON_SYSTEM_MODULE,
REASON_OPT_OUT_REQUESTED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -688,8 +705,8 @@
return "LOCALE_CHANGED";
case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
- case REASON_ACTION_REFRESH_SAFETY_SOURCES:
- return "REASON_ACTION_REFRESH_SAFETY_SOURCES";
+ case REASON_REFRESH_SAFETY_SOURCES:
+ return "REASON_REFRESH_SAFETY_SOURCES";
case REASON_SYSTEM_ALLOW_LISTED:
return "SYSTEM_ALLOW_LISTED";
case REASON_ALARM_MANAGER_ALARM_CLOCK:
@@ -726,6 +743,12 @@
return "SHELL";
case REASON_MEDIA_SESSION_CALLBACK:
return "MEDIA_SESSION_CALLBACK";
+ case REASON_ROLE_DIALER:
+ return "ROLE_DIALER";
+ case REASON_ROLE_EMERGENCY:
+ return "ROLE_EMERGENCY";
+ case REASON_SYSTEM_MODULE:
+ return "SYSTEM_MODULE";
case REASON_OPT_OUT_REQUESTED:
return "REASON_OPT_OUT_REQUESTED";
default:
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 0f36d32..9b1f2d0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -4,8 +4,8 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.AppStandbyInfo;
+import android.app.usage.UsageStatsManager.ForcedReasons;
import android.app.usage.UsageStatsManager.StandbyBuckets;
-import android.app.usage.UsageStatsManager.SystemForcedReasons;
import android.content.Context;
import android.util.IndentingPrintWriter;
@@ -152,7 +152,7 @@
* UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_* reasons.
*/
void restrictApp(@NonNull String packageName, int userId,
- @SystemForcedReasons int restrictReason);
+ @ForcedReasons int restrictReason);
/**
* Put the specified app in the
@@ -169,7 +169,29 @@
* UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_* reasons.
*/
void restrictApp(@NonNull String packageName, int userId, int mainReason,
- @SystemForcedReasons int restrictReason);
+ @ForcedReasons int restrictReason);
+
+ /**
+ * Unrestrict an app if there is no other reason to restrict it.
+ *
+ * <p>
+ * The {@code prevMainReasonRestrict} and {@code prevSubReasonRestrict} are the previous
+ * reasons of why it was restricted, but the caller knows that these conditions are not true
+ * anymore; therefore if there is no other reasons to restrict it (as there could bemultiple
+ * reasons to restrict it), lift the restriction.
+ * </p>
+ *
+ * @param packageName The package name of the app.
+ * @param userId The user id that this app runs in.
+ * @param prevMainReasonRestrict The main reason that why it was restricted, must be either
+ * {@link android.app.usage.UsageStatsManager#REASON_MAIN_FORCED_BY_SYSTEM}
+ * or {@link android.app.usage.UsageStatsManager#REASON_MAIN_FORCED_BY_USER}.
+ * @param prevSubReasonRestrict The subreason that why it was restricted before.
+ * @param mainReasonUnrestrict The main reason that why it could be unrestricted now.
+ * @param subReasonUnrestrict The subreason that why it could be unrestricted now.
+ */
+ void maybeUnrestrictApp(@NonNull String packageName, int userId, int prevMainReasonRestrict,
+ int prevSubReasonRestrict, int mainReasonUnrestrict, int subReasonUnrestrict);
void addActiveDeviceAdmin(String adminPkg, int userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index d21a0ea..70d5038 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
@@ -280,6 +281,14 @@
}
}
+ private final AppBackgroundRestrictionListener mAppBackgroundRestrictionListener =
+ new AppBackgroundRestrictionListener() {
+ @Override
+ public void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+ mHandler.notifyAutoRestrictedBucketFeatureFlagChanged(autoRestrictedBucket);
+ }
+ };
+
/**
* Listener for any state changes that affect any app's eligibility to run.
*/
@@ -370,6 +379,18 @@
}
/**
+ * Called when toggling the feature flag of moving to restricted standby bucket
+ * automatically on background-restricted.
+ */
+ private void onAutoRestrictedBucketFeatureFlagChanged(AppStateTrackerImpl sender,
+ boolean autoRestrictedBucket) {
+ updateAllJobs();
+ if (autoRestrictedBucket) {
+ unblockAllUnrestrictedAlarms();
+ }
+ }
+
+ /**
* Called when the job restrictions for multiple UIDs might have changed, so the job
* scheduler should re-evaluate all restrictions for all jobs.
*/
@@ -499,6 +520,8 @@
mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled();
mStandbyTracker = new StandbyTracker();
mAppStandbyInternal.addListener(mStandbyTracker);
+ mActivityManagerInternal.addAppBackgroundRestrictionListener(
+ mAppBackgroundRestrictionListener);
try {
mIActivityManager.registerUidObserver(new UidObserver(),
@@ -802,6 +825,7 @@
private static final int MSG_USER_REMOVED = 8;
private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
+ private static final int MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED = 11;
private static final int MSG_ON_UID_ACTIVE = 12;
private static final int MSG_ON_UID_GONE = 13;
@@ -849,6 +873,12 @@
obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget();
}
+ public void notifyAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+ removeMessages(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED);
+ obtainMessage(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED,
+ autoRestrictedBucket ? 1 : 0, 0).sendToTarget();
+ }
+
public void doUserRemoved(int userId) {
obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
}
@@ -952,6 +982,13 @@
handleUserRemoved(msg.arg1);
return;
+ case MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED:
+ final boolean autoRestrictedBucket = msg.arg1 == 1;
+ for (Listener l : cloneListeners()) {
+ l.onAutoRestrictedBucketFeatureFlagChanged(sender, autoRestrictedBucket);
+ }
+ return;
+
case MSG_ON_UID_ACTIVE:
handleUidActive(msg.arg1);
return;
@@ -1120,7 +1157,12 @@
if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) {
return false;
}
- return (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName));
+ // If apps will be put into restricted standby bucket automatically on user-forced
+ // app standby, instead of blocking alarms completely, let the restricted standby bucket
+ // policy take care of it.
+ return (mForcedAppStandbyEnabled
+ && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && isRunAnyRestrictedLocked(uid, packageName));
}
}
@@ -1161,7 +1203,12 @@
|| ArrayUtils.contains(mTempExemptAppIds, appId)) {
return false;
}
- if (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)) {
+ // If apps will be put into restricted standby bucket automatically on user-forced
+ // app standby, instead of blocking jobs completely, let the restricted standby bucket
+ // policy take care of it.
+ if (mForcedAppStandbyEnabled
+ && !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && isRunAnyRestrictedLocked(uid, packageName)) {
return true;
}
if (hasForegroundExemption) {
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 d93ad3c..b2ae8ee 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1209,18 +1209,16 @@
synchronized (mLock) {
mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier());
}
- // The user is starting but credential encrypted storage is still locked.
- // Only direct-boot-aware jobs can safely run.
- // Let's kick off any eligible jobs for this user.
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
+ /** Start jobs after user is available, delayed by a few seconds since non-urgent. */
@Override
- public void onUserUnlocked(@NonNull TargetUser user) {
- // The user is fully unlocked and credential encrypted storage is now decrypted.
- // Direct-boot-UNaware jobs can now safely run.
- // Let's kick off any outstanding jobs for this user.
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ public void onUserCompletedEvent(@NonNull TargetUser user, UserCompletedEventType eventType) {
+ if (eventType.includesOnUserStarting() || eventType.includesOnUserUnlocked()) {
+ // onUserStarting: direct-boot-aware jobs can safely run
+ // onUserUnlocked: direct-boot-UNaware jobs can safely run.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
}
@Override
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 0ceab35..65d7121 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
@@ -19,6 +19,7 @@
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.app.ActivityManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -59,6 +60,7 @@
static final int KNOWN_ACTIVE = 1;
static final int KNOWN_INACTIVE = 2;
+ private final ActivityManagerInternal mActivityManagerInternal;
private final AppStateTrackerImpl mAppStateTracker;
private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
@@ -66,6 +68,8 @@
public BackgroundJobsController(JobSchedulerService service) {
super(service);
+ mActivityManagerInternal = (ActivityManagerInternal) Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
LocalServices.getService(AppStateTracker.class));
mAppStateTracker.addListener(mForceAppStandbyListener);
@@ -216,7 +220,8 @@
}
boolean didChange =
jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
- !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
+ !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index ebf42b8..8b26397 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -63,8 +63,8 @@
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager.ForcedReasons;
import android.app.usage.UsageStatsManager.StandbyBuckets;
-import android.app.usage.UsageStatsManager.SystemForcedReasons;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
@@ -1406,13 +1406,13 @@
@Override
public void restrictApp(@NonNull String packageName, int userId,
- @SystemForcedReasons int restrictReason) {
+ @ForcedReasons int restrictReason) {
restrictApp(packageName, userId, REASON_MAIN_FORCED_BY_SYSTEM, restrictReason);
}
@Override
public void restrictApp(@NonNull String packageName, int userId, int mainReason,
- @SystemForcedReasons int restrictReason) {
+ @ForcedReasons int restrictReason) {
if (mainReason != REASON_MAIN_FORCED_BY_SYSTEM
&& mainReason != REASON_MAIN_FORCED_BY_USER) {
Slog.e(TAG, "Tried to restrict app " + packageName + " for an unsupported reason");
@@ -1799,27 +1799,36 @@
* bucket if it was forced into the bucket by the system because it was buggy.
*/
@VisibleForTesting
- void maybeUnrestrictBuggyApp(String packageName, int userId) {
+ void maybeUnrestrictBuggyApp(@NonNull String packageName, int userId) {
+ maybeUnrestrictApp(packageName, userId,
+ REASON_MAIN_FORCED_BY_SYSTEM, REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+ REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_APP_UPDATE);
+ }
+
+ @Override
+ public void maybeUnrestrictApp(@NonNull String packageName, int userId,
+ int prevMainReasonRestrict, int prevSubReasonRestrict,
+ int mainReasonUnrestrict, int subReasonUnrestrict) {
synchronized (mAppIdleLock) {
final long elapsedRealtime = mInjector.elapsedRealtime();
final AppIdleHistory.AppUsageHistory app =
mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime);
if (app.currentBucket != STANDBY_BUCKET_RESTRICTED
- || (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_FORCED_BY_SYSTEM) {
+ || (app.bucketingReason & REASON_MAIN_MASK) != prevMainReasonRestrict) {
return;
}
final int newBucket;
final int newReason;
- if ((app.bucketingReason & REASON_SUB_MASK) == REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY) {
- // If bugginess was the only reason the app should be restricted, then lift it out.
+ if ((app.bucketingReason & REASON_SUB_MASK) == prevSubReasonRestrict) {
+ // If it was the only reason the app should be restricted, then lift it out.
newBucket = STANDBY_BUCKET_RARE;
- newReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_UPDATE;
+ newReason = mainReasonUnrestrict | subReasonUnrestrict;
} else {
- // There's another reason the app was restricted. Remove the buggy bit and call
+ // There's another reason the app was restricted. Remove the subreason bit and call
// it a day.
newBucket = STANDBY_BUCKET_RESTRICTED;
- newReason = app.bucketingReason & ~REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
+ newReason = app.bucketingReason & ~prevSubReasonRestrict;
}
mAppIdleHistory.setAppStandbyBucket(
packageName, userId, elapsedRealtime, newBucket, newReason);
diff --git a/boot/hiddenapi/hiddenapi-max-target-q.txt b/boot/hiddenapi/hiddenapi-max-target-q.txt
index 4832dd1..f70473c 100644
--- a/boot/hiddenapi/hiddenapi-max-target-q.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-q.txt
@@ -398,7 +398,7 @@
Lcom/android/internal/R$layout;->select_dialog_singlechoice:I
Lcom/android/internal/R$layout;->webview_find:I
Lcom/android/internal/R$layout;->zoom_magnify:I
-Lcom/android/internal/R$plurals;->matches_found:I
+Lcom/android/internal/R$string;->matches_found:I
Lcom/android/internal/R$raw;->loaderror:I
Lcom/android/internal/R$raw;->nodomain:I
Lcom/android/internal/R$string;->byteShort:I
diff --git a/core/api/current.txt b/core/api/current.txt
index e086cab..c4df0b3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -153,8 +153,12 @@
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
+ field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING";
+ field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
+ field public static final String REQUEST_COMPANION_PROFILE_COMPUTER = "android.permission.REQUEST_COMPANION_PROFILE_COMPUTER";
field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
+ field public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission.REQUEST_COMPANION_SELF_MANAGED";
field public static final String REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND";
field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
field public static final String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES";
@@ -362,6 +366,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoHandwritingEnabled;
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
@@ -2103,6 +2108,8 @@
field public static final int icon_frame = 16908350; // 0x102003e
field public static final int input = 16908297; // 0x1020009
field public static final int inputArea = 16908318; // 0x102001e
+ field public static final int inputExtractAccessories;
+ field public static final int inputExtractAction;
field public static final int inputExtractEditText = 16908325; // 0x1020025
field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
@@ -3145,22 +3152,22 @@
public static final class AccessibilityService.MagnificationController {
method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler);
- method public float getCenterX();
- method public float getCenterY();
+ method @Deprecated public float getCenterX();
+ method @Deprecated public float getCenterY();
method @NonNull public android.graphics.Region getCurrentMagnificationRegion();
method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig();
- method @NonNull public android.graphics.Region getMagnificationRegion();
- method public float getScale();
+ method @Deprecated @NonNull public android.graphics.Region getMagnificationRegion();
+ method @Deprecated public float getScale();
method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public boolean reset(boolean);
method public boolean resetCurrentMagnification(boolean);
- method public boolean setCenter(float, float, boolean);
+ method @Deprecated public boolean setCenter(float, float, boolean);
method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean);
- method public boolean setScale(float, boolean);
+ method @Deprecated public boolean setScale(float, boolean);
}
public static interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
- method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float);
+ method @Deprecated public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float);
method public default void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, @NonNull android.accessibilityservice.MagnificationConfig);
}
@@ -8890,9 +8897,14 @@
method public int describeContents();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
+ method public boolean isForceConfirmation();
+ method public boolean isSelfManaged();
method public boolean isSingleDevice();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
+ field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING";
+ field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
+ field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER) public static final String DEVICE_PROFILE_COMPUTER = "android.app.role.COMPANION_DEVICE_COMPUTER";
field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
}
@@ -8902,6 +8914,8 @@
method @NonNull public android.companion.AssociationRequest build();
method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
@@ -8937,8 +8951,8 @@
}
public final class CompanionDeviceManager {
- method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
- method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
method @Deprecated public void disassociate(@NonNull String);
method public void disassociate(int);
method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
@@ -8961,13 +8975,11 @@
public abstract class CompanionDeviceService extends android.app.Service {
ctor public CompanionDeviceService();
- method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -11876,6 +11888,7 @@
field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
field public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
+ field public static final String FEATURE_SENSOR_DYNAMIC_HEAD_TRACKER = "android.hardware.sensor.dynamic.head_tracker";
field public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
field public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
field public static final String FEATURE_SENSOR_HEART_RATE_ECG = "android.hardware.sensor.heartrate.ecg";
@@ -16798,7 +16811,7 @@
field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
field public static final long USAGE_CPU_WRITE_RARELY = 32L; // 0x20L
- field public static final long USAGE_FRONT_BUFFER = 1L; // 0x1L
+ field public static final long USAGE_FRONT_BUFFER = 4294967296L; // 0x100000000L
field public static final long USAGE_GPU_COLOR_OUTPUT = 512L; // 0x200L
field public static final long USAGE_GPU_CUBE_MAP = 33554432L; // 0x2000000L
field public static final long USAGE_GPU_DATA_BUFFER = 16777216L; // 0x1000000L
@@ -18658,6 +18671,7 @@
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public void onPrepareStylusHandwriting();
method public boolean onShowInputRequested(int, boolean);
method public void onStartCandidatesView(android.view.inputmethod.EditorInfo, boolean);
method public void onStartInput(android.view.inputmethod.EditorInfo, boolean);
@@ -19859,6 +19873,7 @@
method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
method public android.media.AudioDeviceInfo[] getDevices(int);
method public static int getDirectPlaybackSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+ method @NonNull public java.util.List<android.media.AudioProfile> getDirectProfilesForAttributes(@NonNull android.media.AudioAttributes);
method public int getEncodedSurroundMode();
method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
method public int getMode();
@@ -20559,13 +20574,24 @@
}
public static final class EncoderProfiles.VideoProfile {
+ method public int getBitDepth();
method public int getBitrate();
+ method public int getChromaSubsampling();
method public int getCodec();
method public int getFrameRate();
+ method public int getHdrFormat();
method public int getHeight();
method @NonNull public String getMediaType();
method public int getProfile();
method public int getWidth();
+ field public static final int HDR_DOLBY_VISION = 4; // 0x4
+ field public static final int HDR_HDR10 = 2; // 0x2
+ field public static final int HDR_HDR10PLUS = 3; // 0x3
+ field public static final int HDR_HLG = 1; // 0x1
+ field public static final int HDR_NONE = 0; // 0x0
+ field public static final int YUV_420 = 0; // 0x0
+ field public static final int YUV_422 = 1; // 0x1
+ field public static final int YUV_444 = 2; // 0x2
}
public class ExifInterface {
@@ -21221,9 +21247,11 @@
field public static final int COLOR_Format24bitBGR888 = 12; // 0xc
field @Deprecated public static final int COLOR_Format24bitRGB888 = 11; // 0xb
field @Deprecated public static final int COLOR_Format25bitARGB1888 = 14; // 0xe
+ field public static final int COLOR_Format32bitABGR2101010 = 2130750114; // 0x7f00aaa2
field public static final int COLOR_Format32bitABGR8888 = 2130747392; // 0x7f00a000
field @Deprecated public static final int COLOR_Format32bitARGB8888 = 16; // 0x10
field @Deprecated public static final int COLOR_Format32bitBGRA8888 = 15; // 0xf
+ field public static final int COLOR_Format64bitABGRFloat = 2130710294; // 0x7f000f16
field @Deprecated public static final int COLOR_Format8bitRGB332 = 2; // 0x2
field @Deprecated public static final int COLOR_FormatCbYCrY = 27; // 0x1b
field @Deprecated public static final int COLOR_FormatCrYCbY = 28; // 0x1c
@@ -21346,11 +21374,14 @@
field public static final int AVCProfileHigh422 = 32; // 0x20
field public static final int AVCProfileHigh444 = 64; // 0x40
field public static final int AVCProfileMain = 2; // 0x2
+ field public static final int DolbyVisionLevel8k30 = 1024; // 0x400
+ field public static final int DolbyVisionLevel8k60 = 2048; // 0x800
field public static final int DolbyVisionLevelFhd24 = 4; // 0x4
field public static final int DolbyVisionLevelFhd30 = 8; // 0x8
field public static final int DolbyVisionLevelFhd60 = 16; // 0x10
field public static final int DolbyVisionLevelHd24 = 1; // 0x1
field public static final int DolbyVisionLevelHd30 = 2; // 0x2
+ field public static final int DolbyVisionLevelUhd120 = 512; // 0x200
field public static final int DolbyVisionLevelUhd24 = 32; // 0x20
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
@@ -21649,9 +21680,9 @@
method @NonNull public byte[] getPropertyByteArray(String);
method @NonNull public String getPropertyString(@NonNull String);
method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
- method @NonNull public byte[] getSecureStop(@NonNull byte[]);
- method @NonNull public java.util.List<byte[]> getSecureStopIds();
- method @NonNull public java.util.List<byte[]> getSecureStops();
+ method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]);
+ method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds();
+ method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops();
method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes();
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
@@ -21664,11 +21695,11 @@
method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]);
method @Deprecated public void release();
method @Deprecated public void releaseAllSecureStops();
- method public void releaseSecureStops(@NonNull byte[]);
- method public void removeAllSecureStops();
+ method @Deprecated public void releaseSecureStops(@NonNull byte[]);
+ method @Deprecated public void removeAllSecureStops();
method public void removeKeys(@NonNull byte[]);
method public void removeOfflineLicense(@NonNull byte[]);
- method public void removeSecureStop(@NonNull byte[]);
+ method @Deprecated public void removeSecureStop(@NonNull byte[]);
method public boolean requiresSecureDecoder(@NonNull String);
method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int);
method public void restoreKeys(@NonNull byte[], @NonNull byte[]);
@@ -22669,12 +22700,15 @@
}
public final class MediaRecorder.VideoEncoder {
+ field public static final int AV1 = 8; // 0x8
field public static final int DEFAULT = 0; // 0x0
+ field public static final int DOLBY_VISION = 7; // 0x7
field public static final int H263 = 1; // 0x1
field public static final int H264 = 2; // 0x2
field public static final int HEVC = 5; // 0x5
field public static final int MPEG_4_SP = 3; // 0x3
field public static final int VP8 = 4; // 0x4
+ field public static final int VP9 = 6; // 0x6
}
public final class MediaRecorder.VideoSource {
@@ -24933,6 +24967,156 @@
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AitInfo> CREATOR;
}
+ public abstract class BroadcastInfoRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getOption();
+ method public int getRequestId();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
+ field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+ field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
+ }
+
+ public abstract class BroadcastInfoResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getRequestId();
+ method public int getResponseResult();
+ method public int getSequence();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoResponse> CREATOR;
+ field public static final int RESPONSE_RESULT_CANCEL = 3; // 0x3
+ field public static final int RESPONSE_RESULT_ERROR = 1; // 0x1
+ field public static final int RESPONSE_RESULT_OK = 2; // 0x2
+ }
+
+ public final class CommandRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public CommandRequest(int, int, @NonNull String, @NonNull String, @NonNull String);
+ method @NonNull public String getArguments();
+ method @NonNull public String getName();
+ method @NonNull public String getNameSpace();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.CommandRequest> CREATOR;
+ }
+
+ public final class CommandResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public CommandResponse(int, int, int, @Nullable String);
+ method @Nullable public String getResponse();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.CommandResponse> CREATOR;
+ }
+
+ public final class DsmccRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public DsmccRequest(int, int, @NonNull android.net.Uri);
+ method @NonNull public android.net.Uri getUri();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.DsmccRequest> CREATOR;
+ }
+
+ public final class DsmccResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public DsmccResponse(int, int, int, @Nullable android.os.ParcelFileDescriptor);
+ ctor public DsmccResponse(int, int, int, boolean, @Nullable java.util.List<java.lang.String>);
+ ctor public DsmccResponse(int, int, int, @Nullable int[], @Nullable String[]);
+ method @NonNull public String getBiopMessageType();
+ method @NonNull public java.util.List<java.lang.String> getChildList();
+ method @NonNull public android.os.ParcelFileDescriptor getFile();
+ method @NonNull public int[] getStreamEventIds();
+ method @NonNull public String[] getStreamEventNames();
+ field public static final String BIOP_MESSAGE_TYPE_DIRECTORY = "directory";
+ field public static final String BIOP_MESSAGE_TYPE_FILE = "file";
+ field public static final String BIOP_MESSAGE_TYPE_SERVICE_GATEWAY = "service_gateway";
+ field public static final String BIOP_MESSAGE_TYPE_STREAM = "stream";
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.DsmccResponse> CREATOR;
+ }
+
+ public final class PesRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public PesRequest(int, int, int, int);
+ method public int getStreamId();
+ method public int getTsPid();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.PesRequest> CREATOR;
+ }
+
+ public final class PesResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public PesResponse(int, int, int, @Nullable String);
+ method @Nullable public String getSharedFilterToken();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.PesResponse> CREATOR;
+ }
+
+ public final class SectionRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public SectionRequest(int, int, int, int, int);
+ method public int getTableId();
+ method public int getTsPid();
+ method public int getVersion();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SectionRequest> CREATOR;
+ }
+
+ public final class SectionResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public SectionResponse(int, int, int, int, int, @Nullable android.os.Bundle);
+ method @NonNull public android.os.Bundle getSessionData();
+ method public int getSessionId();
+ method public int getVersion();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SectionResponse> CREATOR;
+ }
+
+ public final class StreamEventRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public StreamEventRequest(int, int, @NonNull android.net.Uri, @NonNull String);
+ method @NonNull public String getEventName();
+ method @NonNull public android.net.Uri getTargetUri();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.StreamEventRequest> CREATOR;
+ }
+
+ public final class StreamEventResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public StreamEventResponse(int, int, int, int, long, @Nullable byte[]);
+ method @Nullable public byte[] getData();
+ method public int getEventId();
+ method public long getNpt();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.StreamEventResponse> CREATOR;
+ }
+
+ public final class TableRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public TableRequest(int, int, int, int, int);
+ method public int getTableId();
+ method public int getTableName();
+ method public int getVersion();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableRequest> CREATOR;
+ field public static final int TABLE_NAME_PAT = 0; // 0x0
+ field public static final int TABLE_NAME_PMT = 1; // 0x1
+ }
+
+ public final class TableResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public TableResponse(int, int, int, @Nullable android.net.Uri, int, int);
+ method public int getSize();
+ method @Nullable public android.net.Uri getTableUri();
+ method public int getVersion();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableResponse> CREATOR;
+ }
+
+ public final class TimelineRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public TimelineRequest(int, int, int);
+ method public int getIntervalMillis();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TimelineRequest> CREATOR;
+ }
+
+ public final class TimelineResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public TimelineResponse(int, int, int, @Nullable String, int, int, long, long);
+ method @Nullable public String getSelector();
+ method public long getTicks();
+ method public int getUnitsPerSecond();
+ method public int getUnitsPerTick();
+ method public long getWallClock();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TimelineResponse> CREATOR;
+ }
+
+ public final class TsRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
+ ctor public TsRequest(int, int, int);
+ method public int getTsPid();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TsRequest> CREATOR;
+ }
+
+ public final class TsResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
+ ctor public TsResponse(int, int, int, @Nullable String);
+ method @Nullable public String getSharedFilterToken();
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TsResponse> CREATOR;
+ }
+
public final class TvContentRating {
method public boolean contains(@NonNull android.media.tv.TvContentRating);
method public static android.media.tv.TvContentRating createRating(String, String, String, java.lang.String...);
@@ -25419,6 +25603,14 @@
field public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
field public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+ field public static final int BROADCAST_INFO_STREAM_EVENT = 5; // 0x5
+ field public static final int BROADCAST_INFO_TYPE_COMMAND = 7; // 0x7
+ field public static final int BROADCAST_INFO_TYPE_DSMCC = 6; // 0x6
+ field public static final int BROADCAST_INFO_TYPE_PES = 4; // 0x4
+ field public static final int BROADCAST_INFO_TYPE_SECTION = 3; // 0x3
+ field public static final int BROADCAST_INFO_TYPE_TABLE = 2; // 0x2
+ field public static final int BROADCAST_INFO_TYPE_TIMELINE = 8; // 0x8
+ field public static final int BROADCAST_INFO_TYPE_TS = 1; // 0x1
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -25508,6 +25700,7 @@
method public void layoutSurface(int, int, int, int);
method public void notifyAdResponse(@NonNull android.media.tv.AdResponse);
method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
+ method public void notifyBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
method public void notifyChannelRetuned(android.net.Uri);
method public void notifyContentAllowed();
method public void notifyContentBlocked(@NonNull android.media.tv.TvContentRating);
@@ -25527,7 +25720,9 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method public void onOverlayViewSizeChanged(int, int);
method public abstract void onRelease();
+ method public void onRemoveBroadcastInfo(int);
method public void onRequestAd(@NonNull android.media.tv.AdRequest);
+ method public void onRequestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
method public boolean onSelectTrack(int, @Nullable String);
method public abstract void onSetCaptionEnabled(boolean);
method public void onSetInteractiveAppNotificationEnabled(boolean);
@@ -25792,6 +25987,7 @@
method public void notifySessionStateChanged(int, int);
method public final void notifyTeletextAppStateChanged(int);
method public void onAdResponse(@NonNull android.media.tv.AdResponse);
+ method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
method public void onContentAllowed();
method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
method public void onCreateBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -25823,7 +26019,9 @@
method public void onTuned(@NonNull android.net.Uri);
method public void onVideoAvailable();
method public void onVideoUnavailable(int);
+ method public void removeBroadcastInfo(int);
method public void requestAd(@NonNull android.media.tv.AdRequest);
+ method public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
method public void requestCurrentChannelLcn();
method public void requestCurrentChannelUri();
method public void requestCurrentTvInputId();
@@ -26152,12 +26350,14 @@
public static final class Ikev2VpnProfile.Builder {
ctor public Ikev2VpnProfile.Builder(@NonNull String, @NonNull String);
+ ctor public Ikev2VpnProfile.Builder(@NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
method @NonNull public android.net.Ikev2VpnProfile build();
method @NonNull public android.net.Ikev2VpnProfile.Builder setAllowedAlgorithms(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey, @Nullable java.security.cert.X509Certificate);
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]);
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate);
method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean);
+ method @NonNull public android.net.Ikev2VpnProfile.Builder setExcludeLocalRoutes(boolean);
method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int);
method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean);
method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo);
@@ -26303,6 +26503,7 @@
}
public abstract class PlatformVpnProfile {
+ method public final boolean getExcludeLocalRoutes();
method public final int getType();
method @NonNull public final String getTypeString();
field public static final int TYPE_IKEV2_IPSEC_PSK = 7; // 0x7
@@ -31261,7 +31462,7 @@
method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
- method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<? super T>);
+ method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
@@ -31271,7 +31472,7 @@
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
method @Deprecated @Nullable public java.io.Serializable readSerializable();
- method @Nullable public <T extends java.io.Serializable> T readSerializable(@Nullable ClassLoader, @NonNull Class<? super T>);
+ method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
method @NonNull public android.util.Size readSize();
method @NonNull public android.util.SizeF readSizeF();
method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
@@ -49420,6 +49621,7 @@
method public boolean isAccessibilityHeading();
method public boolean isActivated();
method public boolean isAttachedToWindow();
+ method public boolean isAutoHandwritingEnabled();
method public boolean isClickable();
method public boolean isContextClickable();
method public boolean isDirty();
@@ -49603,6 +49805,7 @@
method public void setAlpha(@FloatRange(from=0.0, to=1.0) float);
method public void setAnimation(android.view.animation.Animation);
method public void setAnimationMatrix(@Nullable android.graphics.Matrix);
+ method public void setAutoHandwritingEnabled(boolean);
method public void setAutofillHints(@Nullable java.lang.String...);
method public void setAutofillId(@Nullable android.view.autofill.AutofillId);
method public void setBackground(android.graphics.drawable.Drawable);
@@ -52589,6 +52792,9 @@
method public default boolean setImeConsumesInput(boolean);
method public boolean setSelection(int, int);
method @Nullable public default android.view.inputmethod.TextSnapshot takeSnapshot();
+ 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_IMMEDIATE = 1; // 0x1
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
@@ -54094,7 +54300,6 @@
public abstract class WebSettings {
ctor public WebSettings();
method @Deprecated public abstract boolean enableSmoothTransition();
- method public boolean getAllowAlgorithmicDarkening();
method public abstract boolean getAllowContentAccess();
method public abstract boolean getAllowFileAccess();
method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -54139,7 +54344,8 @@
method public abstract int getTextZoom();
method public abstract boolean getUseWideViewPort();
method public abstract String getUserAgentString();
- method public void setAllowAlgorithmicDarkening(boolean);
+ method public boolean isAlgorithmicDarkeningAllowed();
+ method public void setAlgorithmicDarkeningAllowed(boolean);
method public abstract void setAllowContentAccess(boolean);
method public abstract void setAllowFileAccess(boolean);
method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 6c2db43..1f40e59 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -74,6 +74,7 @@
public class NetworkStatsManager {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
+ method public static int getCollapsedRatType(int);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException;
@@ -85,6 +86,7 @@
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean);
+ field public static final int NETWORK_TYPE_5G_NSA = -2; // 0xfffffffe
}
public abstract static class NetworkStatsManager.UsageCallback {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index abbaf45..6694303 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -28,6 +28,8 @@
field public static final String ACCESS_ULTRASOUND = "android.permission.ACCESS_ULTRASOUND";
field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
+ field public static final String ADD_ALWAYS_UNLOCKED_DISPLAY = "android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY";
+ field public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY";
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
@@ -157,6 +159,7 @@
field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
+ field public static final String MANAGE_CLOUDSEARCH = "android.permission.MANAGE_CLOUDSEARCH";
field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
@@ -269,9 +272,6 @@
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
- field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING";
- field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
- field public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission.REQUEST_COMPANION_SELF_MANAGED";
field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -1584,6 +1584,99 @@
}
+package android.app.cloudsearch {
+
+ public class CloudSearchManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_CLOUDSEARCH) public void search(@NonNull android.app.cloudsearch.SearchRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.cloudsearch.CloudSearchManager.CallBack);
+ }
+
+ public static interface CloudSearchManager.CallBack {
+ method public void onSearchFailed(@NonNull android.app.cloudsearch.SearchRequest, @NonNull android.app.cloudsearch.SearchResponse);
+ method public void onSearchSucceeded(@NonNull android.app.cloudsearch.SearchRequest, @NonNull android.app.cloudsearch.SearchResponse);
+ }
+
+ public final class SearchRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public float getMaxLatencyMillis();
+ method @NonNull public String getQuery();
+ method @NonNull public String getRequestId();
+ method public int getResultNumber();
+ method public int getResultOffset();
+ method @NonNull public android.os.Bundle getSearchConstraints();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION";
+ field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER";
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchRequest> CREATOR;
+ }
+
+ public static final class SearchRequest.Builder {
+ ctor public SearchRequest.Builder(@NonNull String);
+ method @NonNull public android.app.cloudsearch.SearchRequest build();
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setMaxLatencyMillis(float);
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setQuery(@NonNull String);
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setResultNumber(int);
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setResultOffset(int);
+ method @NonNull public android.app.cloudsearch.SearchRequest.Builder setSearchConstraints(@Nullable android.os.Bundle);
+ }
+
+ public final class SearchResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.app.cloudsearch.SearchResult> getSearchResults();
+ method @NonNull public String getSource();
+ method public int getStatusCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchResponse> CREATOR;
+ field public static final int SEARCH_STATUS_NO_INTERNET = 2; // 0x2
+ field public static final int SEARCH_STATUS_OK = 0; // 0x0
+ field public static final int SEARCH_STATUS_TIME_OUT = 1; // 0x1
+ field public static final int SEARCH_STATUS_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static final class SearchResponse.Builder {
+ ctor public SearchResponse.Builder(int);
+ method @NonNull public android.app.cloudsearch.SearchResponse build();
+ method @NonNull public android.app.cloudsearch.SearchResponse.Builder setSearchResults(@NonNull java.util.List<android.app.cloudsearch.SearchResult>);
+ method @NonNull public android.app.cloudsearch.SearchResponse.Builder setStatusCode(int);
+ }
+
+ public final class SearchResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.Bundle getExtraInfos();
+ method public float getScore();
+ method @NonNull public String getSnippet();
+ method @NonNull public String getTitle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchResult> CREATOR;
+ field public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "ACTION_BUTTON_IMAGE";
+ field public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "ACTION_BUTTON_TEXT";
+ field public static final String EXTRAINFO_APP_BADGES = "APP_BADGES";
+ field public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER = "APP_CONTAINS_ADS_DISCLAIMER";
+ field public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER = "APP_CONTAINS_IAP_DISCLAIMER";
+ field public static final String EXTRAINFO_APP_DEVELOPER_NAME = "APP_DEVELOPER_NAME";
+ field public static final String EXTRAINFO_APP_DOMAIN_URL = "APP_DOMAIN_URL";
+ field public static final String EXTRAINFO_APP_IARC = "APP_IARC";
+ field public static final String EXTRAINFO_APP_ICON = "APP_ICON";
+ field public static final String EXTRAINFO_APP_REVIEW_COUNT = "APP_REVIEW_COUNT";
+ field public static final String EXTRAINFO_APP_SIZE_BYTES = "APP_SIZE_BYTES";
+ field public static final String EXTRAINFO_APP_STAR_RATING = "APP_STAR_RATING";
+ field public static final String EXTRAINFO_LONG_DESCRIPTION = "LONG_DESCRIPTION";
+ field public static final String EXTRAINFO_SCREENSHOTS = "SCREENSHOTS";
+ field public static final String EXTRAINFO_SHORT_DESCRIPTION = "SHORT_DESCRIPTION";
+ field public static final String EXTRAINFO_WEB_ICON = "WEB_ICON";
+ field public static final String EXTRAINFO_WEB_URL = "WEB_URL";
+ }
+
+ public static final class SearchResult.Builder {
+ ctor public SearchResult.Builder(@NonNull String, @NonNull android.os.Bundle);
+ method @NonNull public android.app.cloudsearch.SearchResult build();
+ method @NonNull public android.app.cloudsearch.SearchResult.Builder setExtraInfos(@NonNull android.os.Bundle);
+ method @NonNull public android.app.cloudsearch.SearchResult.Builder setScore(float);
+ method @NonNull public android.app.cloudsearch.SearchResult.Builder setSnippet(@NonNull String);
+ method @NonNull public android.app.cloudsearch.SearchResult.Builder setTitle(@NonNull String);
+ }
+
+}
+
package android.app.compat {
public final class CompatChanges {
@@ -2161,6 +2254,18 @@
package android.app.usage {
+ public final class BroadcastResponseStats implements android.os.Parcelable {
+ ctor public BroadcastResponseStats(@NonNull String);
+ method public int describeContents();
+ method @IntRange(from=0) public int getBroadcastsDispatchedCount();
+ method @IntRange(from=0) public int getNotificationsCancelledCount();
+ method @IntRange(from=0) public int getNotificationsPostedCount();
+ method @IntRange(from=0) public int getNotificationsUpdatedCount();
+ method @NonNull public String getPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.BroadcastResponseStats> CREATOR;
+ }
+
public final class CacheQuotaHint implements android.os.Parcelable {
ctor public CacheQuotaHint(@NonNull android.app.usage.CacheQuotaHint.Builder);
method public int describeContents();
@@ -2216,11 +2321,13 @@
}
public final class UsageStatsManager {
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long getLastTimeAnyComponentUsed(@NonNull String);
method public int getUsageSource();
method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.BroadcastResponseStats queryBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
@@ -2265,18 +2372,6 @@
method public boolean isSelfManaged();
}
- public final class AssociationRequest implements android.os.Parcelable {
- method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isForceConfirmation();
- method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isSelfManaged();
- field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING";
- field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
- }
-
- public static final class AssociationRequest.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setSelfManaged(boolean);
- }
-
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void addOnAssociationsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
method @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public void associate(@NonNull String, @NonNull android.net.MacAddress, @NonNull byte[]);
@@ -2335,7 +2430,7 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
- method @NonNull @RequiresPermission(value="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY", conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
}
@@ -2402,6 +2497,7 @@
field public static final String BATTERY_STATS_SERVICE = "batterystats";
field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
+ field public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
field public static final String ETHERNET_SERVICE = "ethernet";
@@ -2481,7 +2577,6 @@
field public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED = "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED";
field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
- field public static final String ACTION_REFRESH_SAFETY_SOURCES = "android.intent.action.REFRESH_SAFETY_SOURCES";
field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE = "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE";
@@ -2512,10 +2607,6 @@
field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
field public static final String EXTRA_REASON = "android.intent.extra.REASON";
- field public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0; // 0x0
- field public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1; // 0x1
- field public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE = "android.intent.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE";
- field public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS = "android.intent.extra.REFRESH_SAFETY_SOURCE_IDS";
field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
@@ -5563,6 +5654,7 @@
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
+ method public static boolean isUltrasoundSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
@@ -9018,6 +9110,7 @@
field public static final int REASON_OTHER = 1; // 0x1
field public static final int REASON_PUSH_MESSAGING = 101; // 0x65
field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66
+ field public static final int REASON_REFRESH_SAFETY_SOURCES = 208; // 0xd0
field public static final int REASON_UNKNOWN = 0; // 0x0
field public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
field public static final int TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
@@ -9229,6 +9322,8 @@
method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String, boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getRestrictedProfileParent();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
@@ -10460,6 +10555,17 @@
}
+package android.service.cloudsearch {
+
+ public abstract class CloudSearchService extends android.app.Service {
+ ctor public CloudSearchService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onSearch(@NonNull android.app.cloudsearch.SearchRequest);
+ method public final void returnResults(@NonNull String, @NonNull android.app.cloudsearch.SearchResponse);
+ }
+
+}
+
package android.service.contentcapture {
public final class ActivityEvent implements android.os.Parcelable {
@@ -12969,6 +13075,7 @@
}
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
+ ctor public TelephonyManager.ModemActivityInfoException(int);
method public int getErrorCode();
field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 3d756ba..dbb4274 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1,6 +1,6 @@
// Baseline format: 1.0
ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
-
+
BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
@@ -12,23 +12,23 @@
GenericException: android.app.prediction.AppPredictor#finalize():
-
+
GenericException: android.hardware.location.ContextHubClient#finalize():
-
+
GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
-
+
GenericException: android.service.autofill.augmented.FillWindow#finalize():
-
+
IntentBuilderName: android.app.search.SearchAction#getIntent():
-
+
IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
KotlinKeyword: android.app.Notification#when:
-
+
MissingGetterMatchingBuilder: android.os.NewUserRequest.Builder#setAdmin():
@@ -46,49 +46,49 @@
MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
-
+
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
-
+
MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI:
-
+
MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI:
-
+
MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0:
-
+
MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1:
-
+
MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
-
+
MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
-
+
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
-
+
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0:
-
+
MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
-
+
MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
-
+
MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0:
-
+
NoSettingsProvider: android.provider.Settings.Secure#FAST_PAIR_SCAN_ENABLED:
@@ -100,11 +100,11 @@
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
-
+
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
-
+
ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
-
+
RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount():
@@ -118,103 +118,103 @@
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
-
+
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]):
-
+
SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
-
+
SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
-
+
SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
-
+
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
-
+
SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
-
+
SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.MediaPlayer#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaPlayer#setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler):
@@ -228,74 +228,78 @@
SamShouldBeLast: android.media.MediaPlayer#setOnSubtitleDataListener(android.media.MediaPlayer.OnSubtitleDataListener, android.os.Handler):
SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaPlayer.setOnSubtitleDataListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
-
+
SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
-
+
SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
-
+
SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
-
+
SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String):
-
+
SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long):
-
+
SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long):
-
+
SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long):
-
+
SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long):
-
+
SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object):
-
+
SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
-
+
SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File):
-
+
SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], String, int, String):
SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], android.net.Uri, String):
SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
-
+
SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
-
+
SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
-
+
SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
-
+
SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
-
+
SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
+
+ServiceName: android.content.Context#CLOUDSEARCH_SERVICE:
+ Inconsistent service value; expected `cloudsearch`, was `cloudsearch_service` (Note: Do not change the name of already released services, which will break tools using `adb shell dumpsys`. Instead add `@SuppressLint("ServiceName"))`
+
UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
-
+
UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2303ddb..0bdbd10 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL";
field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+ field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -275,6 +276,10 @@
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
}
+ public final class GameManager {
+ method public void setGameServiceProvider(@Nullable String);
+ }
+
public abstract class HomeVisibilityListener {
ctor public HomeVisibilityListener();
method public abstract void onHomeVisibilityChanged(boolean);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 897bab4..a2259f3 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -29,6 +29,11 @@
}
filegroup {
+ name: "IBinaryTransparencyService.aidl",
+ srcs: ["com/android/internal/os/IBinaryTransparencyService.aidl"],
+}
+
+filegroup {
name: "ITracingServiceProxy.aidl",
srcs: ["android/tracing/ITracingServiceProxy.aidl"],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 479e6bf..6b0aef8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1400,7 +1400,9 @@
* </p>
*
* @return the current magnification scale
+ * @deprecated Use {@link #getMagnificationConfig()} instead
*/
+ @Deprecated
public float getScale() {
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1435,7 +1437,9 @@
*
* @return the unscaled screen-relative X coordinate of the center of
* the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
*/
+ @Deprecated
public float getCenterX() {
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1470,7 +1474,9 @@
*
* @return the unscaled screen-relative Y coordinate of the center of
* the magnified region
+ * @deprecated Use {@link #getMagnificationConfig()} instead
*/
+ @Deprecated
public float getCenterY() {
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1509,7 +1515,9 @@
*
* @return the region of the screen currently active for magnification, or an empty region
* if magnification is not active.
+ * @deprecated Use {@link #getCurrentMagnificationRegion()} instead
*/
+ @Deprecated
@NonNull
public Region getMagnificationRegion() {
final IAccessibilityServiceConnection connection =
@@ -1677,7 +1685,9 @@
* @param animate {@code true} to animate from the current scale or
* {@code false} to set the scale immediately
* @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
*/
+ @Deprecated
public boolean setScale(float scale, boolean animate) {
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1717,7 +1727,9 @@
* @param animate {@code true} to animate from the current viewport
* center or {@code false} to set the center immediately
* @return {@code true} on success, {@code false} on failure
+ * @deprecated Use {@link #setMagnificationConfig(MagnificationConfig, boolean)} instead
*/
+ @Deprecated
public boolean setCenter(float centerX, float centerY, boolean animate) {
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance(mService).getConnection(
@@ -1754,7 +1766,11 @@
* magnification is focused
* @param centerY the new Y coordinate, in unscaled coordinates, around which
* magnification is focused
+ * @deprecated Override
+ * {@link #onMagnificationChanged(MagnificationController, Region, MagnificationConfig)}
+ * instead
*/
+ @Deprecated
void onMagnificationChanged(@NonNull MagnificationController controller,
@NonNull Region region, float scale, float centerX, float centerY);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 38138d8..93d1e03 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7161,11 +7161,6 @@
writer.println(mChangingConfigurations);
writer.print(innerPrefix); writer.print("mCurrentConfig=");
writer.println(mCurrentConfig);
- if (getResources().hasOverrideDisplayAdjustments()) {
- writer.print(innerPrefix);
- writer.print("FixedRotationAdjustments=");
- writer.println(getResources().getDisplayAdjustments().getFixedRotationAdjustments());
- }
mFragments.dumpLoaders(innerPrefix, fd, writer, args);
mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index a140983..89dd9ef 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -65,6 +65,8 @@
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -937,6 +939,121 @@
@EnabledSince(targetSdkVersion = VERSION_CODES.S)
public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L;
+ // The background process restriction levels. The definitions here are meant for internal
+ // bookkeeping only.
+
+ /**
+ * Not a valid restriction level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_UNKNOWN = 0;
+
+ /**
+ * No background restrictions at all, this should NEVER be used
+ * for any process other than selected system processes, currently it's reserved.
+ *
+ * <p>In the future, apps in {@link #RESTRICTION_LEVEL_EXEMPTED} would receive permissive
+ * background restrictions to protect the system from buggy behaviors; in other words,
+ * the {@link #RESTRICTION_LEVEL_EXEMPTED} would not be the truly "unrestricted" state, while
+ * the {@link #RESTRICTION_LEVEL_UNRESTRICTED} here would be the last resort if there is
+ * a strong reason to grant such a capability to a system app. </p>
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_UNRESTRICTED = 10;
+
+ /**
+ * The default background restriction level for the "unrestricted" apps set by the user,
+ * where it'll have the {@link android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND} set to
+ * ALLOWED, being added into the device idle allow list; however there will be still certain
+ * restrictions to apps in this level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_EXEMPTED = 20;
+
+ /**
+ * The default background restriction level for all other apps, they'll be moved between
+ * various standby buckets, including
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_ACTIVE},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_FREQUENT},
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE}.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_ADAPTIVE_BUCKET = 30;
+
+ /**
+ * The background restriction level where the apps will be placed in the restricted bucket
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_RESTRICTED_BUCKET = 40;
+
+ /**
+ * The background restricted level, where apps would get more restrictions,
+ * such as not allowed to launch foreground services besides on TOP.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
+
+ /**
+ * The most restricted level where the apps are considered "in-hibernation",
+ * its package visibility to the rest of the system is limited.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+
+ /**
+ * Not a valid restriction level, it defines the maximum numerical value of restriction level.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_MAX = 100;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_LEVEL_" }, value = {
+ RESTRICTION_LEVEL_UNKNOWN,
+ RESTRICTION_LEVEL_UNRESTRICTED,
+ RESTRICTION_LEVEL_EXEMPTED,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+ RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_MAX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionLevel{}
+
+ /** @hide */
+ public static String restrictionLevelToName(@RestrictionLevel int level) {
+ switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN:
+ return "unknown";
+ case RESTRICTION_LEVEL_UNRESTRICTED:
+ return "unrestricted";
+ case RESTRICTION_LEVEL_EXEMPTED:
+ return "exempted";
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
+ return "adaptive_bucket";
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET:
+ return "restricted_bucket";
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
+ return "background_restricted";
+ case RESTRICTION_LEVEL_HIBERNATION:
+ return "hibernation";
+ case RESTRICTION_LEVEL_MAX:
+ return "max";
+ default:
+ return "";
+ }
+ }
+
/** @hide */
public int getFrontActivityScreenCompatMode() {
try {
@@ -4950,6 +5067,27 @@
}
/**
+ * @return The reason code of whether or not the given UID should be exempted from background
+ * restrictions here.
+ *
+ * <p>
+ * Note: Call it with caution as it'll try to acquire locks in other services.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ @ReasonCode
+ public int getBackgroundRestrictionExemptionReason(int uid) {
+ try {
+ return getService().getBackgroundRestrictionExemptionReason(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PowerExemptionManager.REASON_DENIED;
+ }
+
+ /**
* A subset of immutable pending intent information suitable for caching on the client side.
*
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 96487de..cce7dd3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager.ProcessCapability;
+import android.app.ActivityManager.RestrictionLevel;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -528,9 +529,11 @@
Notification notification, int id, String pkg, @UserIdInt int userId);
/**
- * Un-foreground all foreground services in the given app.
+ * Fully stop the given app's processes without restoring service starts or
+ * bindings, but without the other durable effects of the full-scale
+ * "force stop" intervention.
*/
- public abstract void makeServicesNonForeground(String pkg, @UserIdInt int userId);
+ public abstract void stopAppForUser(String pkg, @UserIdInt int userId);
/**
* If the given app has any FGSs whose notifications are in the given channel,
@@ -712,4 +715,97 @@
*/
void notifyActivityEventChanged();
}
+
+ /**
+ * Get the restriction level of the given UID, if it hosts multiple packages,
+ * return least restricted level.
+ */
+ public abstract @RestrictionLevel int getRestrictionLevel(int uid);
+
+ /**
+ * Get the restriction level of the given package for given user id.
+ */
+ public abstract @RestrictionLevel int getRestrictionLevel(String pkg, @UserIdInt int userId);
+
+ /**
+ * Get whether or not apps would be put into restricted standby bucket automatically
+ * when it's background-restricted.
+ */
+ public abstract boolean isBgAutoRestrictedBucketFeatureFlagEnabled();
+
+ /**
+ * A listener interface, which will be notified on background restriction changes.
+ */
+ public interface AppBackgroundRestrictionListener {
+ /**
+ * Called when the background restriction level of given uid/package is changed.
+ */
+ default void onRestrictionLevelChanged(int uid, String packageName,
+ @RestrictionLevel int newLevel) {
+ }
+
+ /**
+ * Called when toggling the feature flag of moving to restricted standby bucket
+ * automatically on background-restricted.
+ */
+ default void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) {
+ }
+ }
+
+ /**
+ * Register the background restriction listener callback.
+ */
+ public abstract void addAppBackgroundRestrictionListener(
+ @NonNull AppBackgroundRestrictionListener listener);
+
+ /**
+ * A listener interface, which will be notified on foreground service state changes.
+ */
+ public interface ForegroundServiceStateListener {
+ /**
+ * Call when the given process's foreground service state changes.
+ *
+ * @param packageName The package name of the process.
+ * @param uid The UID of the process.
+ * @param pid The pid of the process.
+ * @param started {@code true} if the process transits from non-FGS state to FGS state.
+ */
+ void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started);
+ }
+
+ /**
+ * Register the foreground service state change listener callback.
+ */
+ public abstract void addForegroundServiceStateListener(
+ @NonNull ForegroundServiceStateListener listener);
+
+ /**
+ * A listener interface, which will be notified on the package sends a broadcast.
+ */
+ public interface BroadcastEventListener {
+ /**
+ * Called when the given package/uid is sending a broadcast.
+ */
+ void onSendingBroadcast(String packageName, int uid);
+ }
+
+ /**
+ * Register the broadcast event listener callback.
+ */
+ public abstract void addBroadcastEventListener(@NonNull BroadcastEventListener listener);
+
+ /**
+ * A listener interface, which will be notified on the package binding to a service.
+ */
+ public interface BindServiceEventListener {
+ /**
+ * Called when the given package/uid is binding to a service
+ */
+ void onBindingService(String packageName, int uid);
+ }
+
+ /**
+ * Register the bind service event listener callback.
+ */
+ public abstract void addBindServiceEventListener(@NonNull BindServiceEventListener listener);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ea62714..3825d8d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -175,7 +175,6 @@
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayAdjustments;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -598,12 +597,6 @@
/** The options for scene transition. */
ActivityOptions mActivityOptions;
- /**
- * If non-null, the activity is launching with a specified rotation, the adjustments should
- * be consumed before activity creation.
- */
- FixedRotationAdjustments mPendingFixedRotationAdjustments;
-
/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;
@@ -625,8 +618,7 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
- IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments,
- IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -646,7 +638,6 @@
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
mActivityOptions = activityOptions;
- mPendingFixedRotationAdjustments = fixedRotationAdjustments;
mLaunchedFromBubble = launchedFromBubble;
init();
}
@@ -3512,21 +3503,6 @@
mH.sendMessage(msg);
}
- private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
- if (DEBUG_MESSAGES) Slog.v(
- TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
- "seq= " + seq);
- Message msg = Message.obtain();
- msg.what = what;
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = obj;
- args.argi1 = arg1;
- args.argi2 = arg2;
- args.argi3 = seq;
- msg.obj = args;
- mH.sendMessage(msg);
- }
-
final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo();
@@ -3536,64 +3512,6 @@
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- /**
- * Applies the rotation adjustments to override display information in resources belong to the
- * provided token. If the token is activity token, the adjustments also apply to application
- * because the appearance of activity is usually more sensitive to the application resources.
- *
- * @param token The token to apply the adjustments.
- * @param fixedRotationAdjustments The information to override the display adjustments of
- * corresponding resources. If it is null, the exiting override
- * will be cleared.
- */
- @Override
- public void handleFixedRotationAdjustments(@NonNull IBinder token,
- @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
- final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
- ? displayAdjustments -> displayAdjustments
- .setFixedRotationAdjustments(fixedRotationAdjustments)
- : null;
- if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
- // No resources are associated with the token.
- return;
- }
- if (mActivities.get(token) == null) {
- // Nothing to do for application if it is not an activity token.
- return;
- }
-
- overrideApplicationDisplayAdjustments(token, override);
- }
-
- /**
- * Applies the last override to application resources for compatibility. Because the Resources
- * of Display can be from application, e.g.
- * applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
- * and the deprecated usage:
- * applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
- *
- * @param token The owner and target of the override.
- * @param override The display adjustments override for application resources. If it is null,
- * the override of the token will be removed and pop the last one to use.
- */
- private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
- @Nullable Consumer<DisplayAdjustments> override) {
- final Consumer<DisplayAdjustments> appOverride;
- if (mActiveRotationAdjustments == null) {
- mActiveRotationAdjustments = new ArrayList<>(2);
- }
- if (override != null) {
- mActiveRotationAdjustments.add(Pair.create(token, override));
- appOverride = override;
- } else {
- mActiveRotationAdjustments.removeIf(adjustmentsPair -> adjustmentsPair.first == token);
- appOverride = mActiveRotationAdjustments.isEmpty()
- ? null
- : mActiveRotationAdjustments.get(mActiveRotationAdjustments.size() - 1).second;
- }
- mInitialApplication.getResources().overrideDisplayAdjustments(appOverride);
- }
-
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
@@ -3811,19 +3729,6 @@
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
- // The rotation adjustments must be applied before creating the activity, so the activity
- // can get the adjusted display info during creation.
- if (r.mPendingFixedRotationAdjustments != null) {
- // The adjustments should have been set by handleLaunchActivity, so the last one is the
- // override for activity resources.
- if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
- mResourcesManager.overrideTokenDisplayAdjustments(r.token,
- mActiveRotationAdjustments.get(
- mActiveRotationAdjustments.size() - 1).second);
- }
- r.mPendingFixedRotationAdjustments = null;
- }
-
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
@@ -3859,13 +3764,6 @@
mProfiler.startProfiling();
}
- if (r.mPendingFixedRotationAdjustments != null) {
- // The rotation adjustments must be applied before handling configuration, so process
- // level display metrics can be adjusted.
- overrideApplicationDisplayAdjustments(r.token, adjustments ->
- adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments));
- }
-
// Make sure we are running with the most recent config.
mConfigurationController.handleConfigurationChanged(null, null);
@@ -5974,13 +5872,6 @@
final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
- final Resources res = activity.getResources();
- if (res.hasOverrideDisplayAdjustments()) {
- // If fixed rotation is applied while the activity is visible (e.g. PiP), the rotated
- // configuration of activity may be sent later than the adjustments. In this case, the
- // adjustments need to be updated for the consistency of display info.
- res.getDisplayAdjustments().getConfiguration().updateFrom(finalOverrideConfig);
- }
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
@@ -7643,8 +7534,7 @@
// We need to apply this change to the resources immediately, because upon returning
// the view hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResources(globalConfig,
- null /* compat */,
- mInitialApplication.getResources().getDisplayAdjustments())) {
+ null /* compat */)) {
mConfigurationController.updateLocaleListFromAppContext(
mInitialApplication.getApplicationContext());
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 6261950..877e7d3 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -119,7 +119,7 @@
for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
WeakReference<ExitTransitionCoordinator> oldRef
= mExitTransitionCoordinators.valueAt(i);
- if (oldRef.get() == null) {
+ if (oldRef.refersTo(null)) {
mExitTransitionCoordinators.removeAt(i);
}
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d365269..65e6ab7 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -27,7 +27,6 @@
import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
@@ -187,10 +186,6 @@
/** Deliver app configuration change notification. */
public abstract void handleConfigurationChanged(Configuration config);
- /** Apply addition adjustments to override display information. */
- public abstract void handleFixedRotationAdjustments(IBinder token,
- FixedRotationAdjustments fixedRotationAdjustments);
-
/**
* Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
* provided token.
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 58f60a6..1a77b65 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -185,14 +185,7 @@
final Application app = mActivityThread.getApplication();
final Resources appResources = app.getResources();
- if (appResources.hasOverrideDisplayAdjustments()) {
- // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments,
- // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated
- // configuration also needs to set to the adjustments for consistency.
- appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
- }
- mResourcesManager.applyConfigurationToResources(config, compat,
- appResources.getDisplayAdjustments());
+ mResourcesManager.applyConfigurationToResources(config, compat);
updateLocaleListFromAppContext(app.getApplicationContext());
if (mConfiguration == null) {
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 76471d3..289b348 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -23,6 +23,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.content.Context;
import android.os.Handler;
@@ -204,4 +205,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Sets the game service provider to the given package name for test only.
+ *
+ * <p>Passing in {@code null} will clear a previously set value.
+ * @hide
+ */
+ @TestApi
+ public void setGameServiceProvider(@Nullable String packageName) {
+ try {
+ mService.setGameServiceProvider(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a2578d6..82c419a 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -279,7 +279,7 @@
List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState();
boolean clearApplicationUserData(in String packageName, boolean keepState,
in IPackageDataObserver observer, int userId);
- void makeServicesNonForeground(in String packageName, int userId);
+ void stopAppForUser(in String packageName, int userId);
/** Returns {@code false} if the callback could not be registered, {@true} otherwise. */
boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback);
@UnsupportedAppUsage
@@ -743,4 +743,14 @@
/** Blocks until all broadcast queues become idle. */
void waitForBroadcastIdle();
+
+ /**
+ * @return The reason code of whether or not the given UID should be exempted from background
+ * restrictions here.
+ *
+ * <p>
+ * Note: Call it with caution as it'll try to acquire locks in other services.
+ * </p>
+ */
+ int getBackgroundRestrictionExemptionReason(int uid);
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 57de8c7..3ea07676 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -29,4 +29,5 @@
boolean getAngleEnabled(String packageName, int userId);
void setGameState(String packageName, in GameState gameState, int userId);
GameModeInfo getGameModeInfo(String packageName, int userId);
+ void setGameServiceProvider(String packageName);
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index bf3778d..2a1883d 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -65,7 +65,6 @@
import java.util.List;
import java.util.Objects;
import java.util.WeakHashMap;
-import java.util.function.Consumer;
import java.util.function.Function;
/** @hide */
@@ -343,6 +342,25 @@
return dm;
}
+ /**
+ * Like getDisplayMetrics, but will adjust the result based on the display information in
+ * config. This is used to make sure that the global configuration matches the activity's
+ * apparent display.
+ */
+ private DisplayMetrics getDisplayMetrics(Configuration config) {
+ final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
+ final DisplayMetrics dm = new DisplayMetrics();
+ final DisplayInfo displayInfo = displayManagerGlobal != null
+ ? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null;
+ if (displayInfo != null) {
+ displayInfo.getAppMetrics(dm,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config);
+ } else {
+ dm.setToDefaults();
+ }
+ return dm;
+ }
+
private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
@NonNull Configuration config) {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
@@ -678,8 +696,7 @@
int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
- ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
- if (resourceImpl == impl) {
+ if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) {
return mResourceImpls.keyAt(i);
}
}
@@ -1312,12 +1329,6 @@
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
- return applyConfigurationToResources(config, compat, null /* adjustments */);
- }
-
- /** Applies the global configuration to the managed resources. */
- public final boolean applyConfigurationToResources(@NonNull Configuration config,
- @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
synchronized (mLock) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
@@ -1347,12 +1358,7 @@
applyAllPendingAppInfoUpdates();
}
- DisplayMetrics displayMetrics = getDisplayMetrics();
- if (adjustments != null) {
- // Currently the only case where the adjustment takes effect is to simulate
- // placing an app in a rotated display.
- adjustments.adjustGlobalAppMetrics(displayMetrics);
- }
+ final DisplayMetrics displayMetrics = getDisplayMetrics(config);
Resources.updateSystemConfiguration(config, displayMetrics, compat);
ApplicationPackageManager.configurationChanged();
@@ -1592,41 +1598,6 @@
}
}
- /**
- * Overrides the display adjustments of all resources which are associated with the given token.
- *
- * @param token The token that owns the resources.
- * @param override The operation to override the existing display adjustments. If it is null,
- * the override adjustments will be cleared.
- * @return {@code true} if the override takes effect.
- */
- public boolean overrideTokenDisplayAdjustments(IBinder token,
- @Nullable Consumer<DisplayAdjustments> override) {
- boolean handled = false;
- synchronized (mLock) {
- final ActivityResources tokenResources = mActivityResourceReferences.get(token);
- if (tokenResources == null) {
- return false;
- }
- final ArrayList<ActivityResource> resourcesRefs = tokenResources.activityResources;
- for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
- final ActivityResource activityResource = resourcesRefs.get(i);
- if (activityResource.overrideDisplayId != null) {
- // This resource overrides the display of the token so we should not be
- // modifying its display adjustments here.
- continue;
- }
-
- final Resources res = activityResource.resources.get();
- if (res != null) {
- res.overrideDisplayAdjustments(override);
- handled = true;
- }
- }
- }
- return handled;
- }
-
private class UpdateHandler implements Resources.UpdateCallbacks {
/**
@@ -1671,7 +1642,7 @@
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
- if (impl == null || impl.get() == null
+ if (impl == null || impl.refersTo(null)
|| !ArrayUtils.contains(key.mLoaders, loader)) {
continue;
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 64d3a9f..56c301f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -44,6 +44,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -214,6 +215,34 @@
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
/**
+ * Session flag for {@link #registerSessionListener} indicating the listener
+ * is interested in sessions on the keygaurd
+ * @hide
+ */
+ public static final int SESSION_KEYGUARD = 1 << 0;
+
+ /**
+ * Session flag for {@link #registerSessionListener} indicating the current session
+ * is interested in session on the biometric prompt.
+ * @hide
+ */
+ public static final int SESSION_BIOMETRIC_PROMPT = 1 << 1;
+
+ /** @hide */
+ public static final Set<Integer> ALL_SESSIONS = Set.of(
+ SESSION_KEYGUARD,
+ SESSION_BIOMETRIC_PROMPT
+ );
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "SESSION_KEYGUARD" }, value = {
+ SESSION_KEYGUARD,
+ SESSION_BIOMETRIC_PROMPT,
+ })
+ public @interface SessionFlags {}
+
+ /**
* Response indicating that the tile was not added.
*/
public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7f8e46e..dd832c8 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -28,6 +28,8 @@
import android.app.ambientcontext.IAmbientContextEventObserver;
import android.app.appsearch.AppSearchManagerFrameworkInitializer;
import android.app.blob.BlobStoreManagerFrameworkInitializer;
+import android.app.cloudsearch.CloudSearchManager;
+import android.app.cloudsearch.ICloudSearchManager;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.job.JobSchedulerFrameworkInitializer;
@@ -213,6 +215,7 @@
import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyRegistryManager;
+import android.transparency.BinaryTransparencyManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -243,6 +246,7 @@
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.graphics.fonts.IFontManager;
import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import com.android.internal.util.Preconditions;
@@ -494,6 +498,17 @@
return new DropBoxManager(ctx, service);
}});
+ registerService(Context.BINARY_TRANSPARENCY_SERVICE, BinaryTransparencyManager.class,
+ new CachedServiceFetcher<BinaryTransparencyManager>() {
+ @Override
+ public BinaryTransparencyManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.BINARY_TRANSPARENCY_SERVICE);
+ IBinaryTransparencyService service = IBinaryTransparencyService.Stub.asInterface(b);
+ return new BinaryTransparencyManager(ctx, service);
+ }});
+
registerService(Context.INPUT_SERVICE, InputManager.class,
new StaticServiceFetcher<InputManager>() {
@Override
@@ -1248,6 +1263,17 @@
}
});
+ registerService(Context.CLOUDSEARCH_SERVICE, CloudSearchManager.class,
+ new CachedServiceFetcher<CloudSearchManager>() {
+ @Override
+ public CloudSearchManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE);
+ return b == null ? null :
+ new CloudSearchManager(ICloudSearchManager.Stub.asInterface(b));
+ }
+ });
+
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
new CachedServiceFetcher<AppPredictionManager>() {
@Override
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 4ff7924..b791f05 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -37,6 +37,7 @@
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.WindowManager;
import java.io.IOException;
@@ -74,6 +75,13 @@
private final Rect mMaxBounds = new Rect();
/**
+ * The rotation of this window's apparent display. This can differ from mRotation in some
+ * situations (like letterbox).
+ */
+ @Surface.Rotation
+ private int mDisplayRotation = ROTATION_UNDEFINED;
+
+ /**
* The current rotation of this window container relative to the default
* orientation of the display it is on (regardless of how deep in the hierarchy
* it is). It is used by the configuration hierarchy to apply rotation-dependent
@@ -196,6 +204,9 @@
/** Bit that indicates that the {@link #mDisplayWindowingMode} changed.
* @hide */
public static final int WINDOW_CONFIG_DISPLAY_WINDOWING_MODE = 1 << 7;
+ /** Bit that indicates that the apparent-display changed.
+ * @hide */
+ public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 8;
/** @hide */
@IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
@@ -207,6 +218,7 @@
WINDOW_CONFIG_ALWAYS_ON_TOP,
WINDOW_CONFIG_ROTATION,
WINDOW_CONFIG_DISPLAY_WINDOWING_MODE,
+ WINDOW_CONFIG_DISPLAY_ROTATION,
})
public @interface WindowConfig {}
@@ -237,6 +249,7 @@
dest.writeInt(mAlwaysOnTop);
dest.writeInt(mRotation);
dest.writeInt(mDisplayWindowingMode);
+ dest.writeInt(mDisplayRotation);
}
/** @hide */
@@ -249,6 +262,7 @@
mAlwaysOnTop = source.readInt();
mRotation = source.readInt();
mDisplayWindowingMode = source.readInt();
+ mDisplayRotation = source.readInt();
}
@Override
@@ -318,6 +332,14 @@
}
/**
+ * Sets the apparent display cutout.
+ * @hide
+ */
+ public void setDisplayRotation(@Surface.Rotation int rotation) {
+ mDisplayRotation = rotation;
+ }
+
+ /**
* Sets whether this window should be always on top.
* @param alwaysOnTop {@code true} to set window always on top, otherwise {@code false}
* @hide
@@ -359,6 +381,14 @@
return mMaxBounds;
}
+ /**
+ * @see #setDisplayRotation
+ * @hide
+ */
+ public @Surface.Rotation int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
public int getRotation() {
return mRotation;
}
@@ -413,6 +443,7 @@
setBounds(other.mBounds);
setAppBounds(other.mAppBounds);
setMaxBounds(other.mMaxBounds);
+ setDisplayRotation(other.mDisplayRotation);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
setAlwaysOnTop(other.mAlwaysOnTop);
@@ -431,6 +462,7 @@
setAppBounds(null);
setBounds(null);
setMaxBounds(null);
+ setDisplayRotation(ROTATION_UNDEFINED);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
setAlwaysOnTop(ALWAYS_ON_TOP_UNDEFINED);
@@ -485,6 +517,11 @@
changed |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
setDisplayWindowingMode(delta.mDisplayWindowingMode);
}
+ if (delta.mDisplayRotation != ROTATION_UNDEFINED
+ && delta.mDisplayRotation != mDisplayRotation) {
+ changed |= WINDOW_CONFIG_DISPLAY_ROTATION;
+ setDisplayRotation(delta.mDisplayRotation);
+ }
return changed;
}
@@ -517,6 +554,9 @@
if ((mask & WINDOW_CONFIG_DISPLAY_WINDOWING_MODE) != 0) {
setDisplayWindowingMode(delta.mDisplayWindowingMode);
}
+ if ((mask & WINDOW_CONFIG_DISPLAY_ROTATION) != 0) {
+ setDisplayRotation(delta.mDisplayRotation);
+ }
}
/**
@@ -573,6 +613,11 @@
changes |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
}
+ if ((compareUndefined || other.mDisplayRotation != ROTATION_UNDEFINED)
+ && mDisplayRotation != other.mDisplayRotation) {
+ changes |= WINDOW_CONFIG_DISPLAY_ROTATION;
+ }
+
return changes;
}
@@ -620,8 +665,11 @@
if (n != 0) return n;
n = mRotation - that.mRotation;
if (n != 0) return n;
+
n = mDisplayWindowingMode - that.mDisplayWindowingMode;
if (n != 0) return n;
+ n = mDisplayRotation - that.mDisplayRotation;
+ if (n != 0) return n;
// if (n != 0) return n;
return n;
@@ -650,6 +698,7 @@
result = 31 * result + mAlwaysOnTop;
result = 31 * result + mRotation;
result = 31 * result + mDisplayWindowingMode;
+ result = 31 * result + mDisplayRotation;
return result;
}
@@ -659,6 +708,8 @@
return "{ mBounds=" + mBounds
+ " mAppBounds=" + mAppBounds
+ " mMaxBounds=" + mMaxBounds
+ + " mDisplayRotation=" + (mRotation == ROTATION_UNDEFINED
+ ? "undefined" : rotationToString(mDisplayRotation))
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mDisplayWindowingMode=" + windowingModeToString(mDisplayWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType)
diff --git a/core/java/android/app/cloudsearch/CloudSearchManager.java b/core/java/android/app/cloudsearch/CloudSearchManager.java
new file mode 100644
index 0000000..471e423
--- /dev/null
+++ b/core/java/android/app/cloudsearch/CloudSearchManager.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cloudsearch;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.util.concurrent.Executor;
+/**
+ * A {@link CloudSearchManager} is the class having all the information passed to search providers.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.CLOUDSEARCH_SERVICE)
+public class CloudSearchManager {
+ /**
+ * CallBack Interface for this API.
+ */
+ public interface CallBack {
+ /**
+ * Invoked by receiving app with the result of the search.
+ *
+ * @param request original request for the search.
+ * @param response search result.
+ */
+ void onSearchSucceeded(@NonNull SearchRequest request, @NonNull SearchResponse response);
+
+ /**
+ * Invoked when the search is not successful.
+ * Each failure is recorded. The client may receive a failure from one provider and
+ * subsequently receive successful searches from other providers
+ *
+ * @param request original request for the search.
+ * @param response search result.
+ */
+ void onSearchFailed(@NonNull SearchRequest request, @NonNull SearchResponse response);
+ }
+
+ private final ICloudSearchManager mService;
+
+ /** @hide **/
+ public CloudSearchManager(@NonNull ICloudSearchManager service) {
+ mService = service;
+ }
+
+ /**
+ * Execute an {@link android.app.cloudsearch.SearchRequest} from the given parameters
+ * to the designated cloud lookup services. After the lookup is done, the given
+ * callback will be invoked by the system with the result or lack thereof.
+ *
+ * @param request request to be searched.
+ * @param callbackExecutor where the callback is invoked.
+ * @param callback invoked when the result is available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_CLOUDSEARCH)
+ public void search(@NonNull SearchRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull CallBack callback) {
+ try {
+ mService.search(
+ requireNonNull(request),
+ new CallBackWrapper(
+ requireNonNull(request),
+ requireNonNull(callback),
+ requireNonNull(callbackExecutor)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final class CallBackWrapper extends
+ ICloudSearchManagerCallback.Stub {
+ @NonNull
+ private final SearchRequest mSearchRequest;
+
+ @NonNull
+ private final CallBack mCallback;
+
+ @NonNull
+ private final Executor mCallbackExecutor;
+
+ CallBackWrapper(
+ SearchRequest searchRequest,
+ CallBack callback,
+ Executor callbackExecutor) {
+ mSearchRequest = searchRequest;
+ mCallback = callback;
+ mCallbackExecutor = callbackExecutor;
+ }
+
+
+ @Override
+ public void onSearchSucceeded(SearchResponse searchResponse) {
+ mCallbackExecutor.execute(
+ () -> mCallback.onSearchSucceeded(mSearchRequest, searchResponse));
+ }
+
+ @Override
+ public void onSearchFailed(SearchResponse searchResponse) {
+ mCallbackExecutor.execute(
+ () -> mCallback.onSearchFailed(mSearchRequest, searchResponse));
+ }
+ }
+}
diff --git a/core/java/android/app/cloudsearch/ICloudSearchManager.aidl b/core/java/android/app/cloudsearch/ICloudSearchManager.aidl
new file mode 100644
index 0000000..18f8fc4
--- /dev/null
+++ b/core/java/android/app/cloudsearch/ICloudSearchManager.aidl
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cloudsearch;
+
+import android.app.cloudsearch.SearchRequest;
+import android.app.cloudsearch.SearchResponse;
+import android.app.cloudsearch.ICloudSearchManagerCallback;
+
+/**
+ * Used by {@link CloudSearchManager} to tell system server to do search.
+ *
+ * @hide
+ */
+oneway interface ICloudSearchManager {
+ void search(in SearchRequest request, in ICloudSearchManagerCallback callBack);
+
+ void returnResults(in IBinder token, in String requestId,
+ in SearchResponse response);
+}
diff --git a/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl b/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl
new file mode 100644
index 0000000..84771dd
--- /dev/null
+++ b/core/java/android/app/cloudsearch/ICloudSearchManagerCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cloudsearch;
+
+import android.app.cloudsearch.SearchResponse;
+
+
+/**
+ * Callback used by system server to notify invoker of {@link CloudSearchManager} of the result
+ *
+ * @hide
+ */
+oneway interface ICloudSearchManagerCallback {
+ void onSearchSucceeded(in SearchResponse response);
+
+ void onSearchFailed(in SearchResponse response);
+}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/app/cloudsearch/SearchRequest.aidl
similarity index 72%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/android/app/cloudsearch/SearchRequest.aidl
index 5206831..9f2cdb8 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/app/cloudsearch/SearchRequest.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.app.cloudsearch;
-/**
- * @hide
- */
-parcelable SelectionContext;
+parcelable SearchRequest;
diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java
new file mode 100644
index 0000000..0c5c30c
--- /dev/null
+++ b/core/java/android/app/cloudsearch/SearchRequest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cloudsearch;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A {@link SearchRequest} is the data class having all the information passed to search providers.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SearchRequest implements Parcelable {
+
+ /**
+ * Query for search.
+ */
+ @NonNull
+ private final String mQuery;
+
+ /**
+ * Expected result offset for pagination.
+ *
+ * The default value is 0.
+ */
+ private final int mResultOffset;
+
+ /**
+ * Expected search result number.
+ *
+ * The default value is 10.
+ */
+ private final int mResultNumber;
+
+ /**
+ * The max acceptable latency.
+ *
+ * The default value is 200 milliseconds.
+ */
+ private final float mMaxLatencyMillis;
+
+ @Nullable
+ private String mId = null;
+
+ /**
+ * List of public static KEYS for the Bundle to mSearchConstraints. mSearchConstraints
+ * contains various constraints specifying the search intent.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = {"CONSTRAINT_"},
+ value = {CONSTRAINT_IS_PRESUBMIT_SUGGESTION,
+ CONSTRAINT_SEARCH_PROVIDER_FILTER})
+ public @interface SearchConstraintKey {}
+ /** If this is a presubmit suggestion, Boolean value expected.
+ * presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or
+ * "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit.
+ */
+ public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION";
+ /** The target search provider list of package names(separated by ;), String value expected.
+ * If this is not provided or its value is empty, then no filter will be applied.
+ */
+ public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER";
+
+ @NonNull
+ private Bundle mSearchConstraints;
+
+ private SearchRequest(Parcel in) {
+ this.mQuery = in.readString();
+ this.mResultOffset = in.readInt();
+ this.mResultNumber = in.readInt();
+ this.mMaxLatencyMillis = in.readFloat();
+ this.mSearchConstraints = in.readBundle();
+ this.mId = in.readString();
+ }
+
+ private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis,
+ Bundle searchConstraints) {
+ mQuery = query;
+ mResultOffset = resultOffset;
+ mResultNumber = resultNumber;
+ mMaxLatencyMillis = maxLatencyMillis;
+ mSearchConstraints = searchConstraints;
+ }
+
+ /** Returns the original query. */
+ @NonNull
+ public String getQuery() {
+ return mQuery;
+ }
+
+ /** Returns the result offset. */
+ public int getResultOffset() {
+ return mResultOffset;
+ }
+
+ /** Returns the expected number of search results. */
+ public int getResultNumber() {
+ return mResultNumber;
+ }
+
+ /** Returns the maximum latency requirement. */
+ public float getMaxLatencyMillis() {
+ return mMaxLatencyMillis;
+ }
+
+ /** Returns the search constraints. */
+ @NonNull
+ public Bundle getSearchConstraints() {
+ return mSearchConstraints;
+ }
+
+ /** Returns the search request id, which is used to identify the request. */
+ @NonNull
+ public String getRequestId() {
+ if (mId == null || mId.length() == 0) {
+ boolean isPresubmit =
+ mSearchConstraints.containsKey(CONSTRAINT_IS_PRESUBMIT_SUGGESTION)
+ && mSearchConstraints.getBoolean(CONSTRAINT_IS_PRESUBMIT_SUGGESTION);
+
+ String searchProvider = "EMPTY";
+ if (mSearchConstraints.containsKey(CONSTRAINT_SEARCH_PROVIDER_FILTER)) {
+ searchProvider = mSearchConstraints.getString(CONSTRAINT_SEARCH_PROVIDER_FILTER);
+ }
+
+ String rawContent = String.format("%s\t%d\t%d\t%f\t%b\t%s",
+ mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
+ isPresubmit, searchProvider);
+
+ mId = String.valueOf(rawContent.hashCode());
+ }
+
+ return mId;
+ }
+
+ private SearchRequest(Builder b) {
+ mQuery = requireNonNull(b.mQuery);
+ mResultOffset = b.mResultOffset;
+ mResultNumber = b.mResultNumber;
+ mMaxLatencyMillis = b.mMaxLatencyMillis;
+ mSearchConstraints = requireNonNull(b.mSearchConstraints);
+ }
+
+ /**
+ * @see Creator
+ *
+ */
+ @NonNull
+ public static final Creator<SearchRequest> CREATOR = new Creator<SearchRequest>() {
+ @Override
+ public SearchRequest createFromParcel(Parcel p) {
+ return new SearchRequest(p);
+ }
+
+ @Override
+ public SearchRequest[] newArray(int size) {
+ return new SearchRequest[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(this.mQuery);
+ dest.writeInt(this.mResultOffset);
+ dest.writeInt(this.mResultNumber);
+ dest.writeFloat(this.mMaxLatencyMillis);
+ dest.writeBundle(this.mSearchConstraints);
+ dest.writeString(getRequestId());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ SearchRequest that = (SearchRequest) obj;
+ return Objects.equals(mQuery, that.mQuery)
+ && mResultOffset == that.mResultOffset
+ && mResultNumber == that.mResultNumber
+ && mMaxLatencyMillis == that.mMaxLatencyMillis
+ && Objects.equals(mSearchConstraints, that.mSearchConstraints);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
+ mSearchConstraints);
+ }
+
+ /**
+ * The builder for {@link SearchRequest}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private String mQuery;
+ private int mResultOffset;
+ private int mResultNumber;
+ private float mMaxLatencyMillis;
+ private Bundle mSearchConstraints;
+
+ /**
+ *
+ * @param query the query for search.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull String query) {
+ mQuery = query;
+
+ mResultOffset = 0;
+ mResultNumber = 10;
+ mMaxLatencyMillis = 200;
+ mSearchConstraints = Bundle.EMPTY;
+ }
+
+ /** Sets the input query. */
+ @NonNull
+ public Builder setQuery(@NonNull String query) {
+ this.mQuery = query;
+ return this;
+ }
+
+ /** Sets the search result offset. */
+ @NonNull
+ public Builder setResultOffset(int resultOffset) {
+ this.mResultOffset = resultOffset;
+ return this;
+ }
+
+ /** Sets the expected number of search result. */
+ @NonNull
+ public Builder setResultNumber(int resultNumber) {
+ this.mResultNumber = resultNumber;
+ return this;
+ }
+
+ /** Sets the maximum acceptable search latency. */
+ @NonNull
+ public Builder setMaxLatencyMillis(float maxLatencyMillis) {
+ this.mMaxLatencyMillis = maxLatencyMillis;
+ return this;
+ }
+
+ /** Sets the search constraints, such as the user location, the search type(presubmit or
+ * postsubmit), and the target search providers. */
+ @NonNull
+ public Builder setSearchConstraints(@Nullable Bundle searchConstraints) {
+ this.mSearchConstraints = searchConstraints;
+ return this;
+ }
+
+ /** Builds a SearchRequest based-on the given params. */
+ @NonNull
+ public SearchRequest build() {
+ if (mQuery == null || mResultOffset < 0 || mResultNumber < 1 || mMaxLatencyMillis < 0
+ || mSearchConstraints == null) {
+ throw new IllegalStateException("Please make sure all required args are valid.");
+ }
+
+ return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis,
+ mSearchConstraints);
+ }
+ }
+}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/app/cloudsearch/SearchResponse.aidl
similarity index 72%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/android/app/cloudsearch/SearchResponse.aidl
index 5206831..2064d11 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/app/cloudsearch/SearchResponse.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.app.cloudsearch;
-/**
- * @hide
- */
-parcelable SelectionContext;
+parcelable SearchResponse;
\ No newline at end of file
diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java
new file mode 100644
index 0000000..607bd56
--- /dev/null
+++ b/core/java/android/app/cloudsearch/SearchResponse.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cloudsearch;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SearchResponse} includes search results and associated meta information.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SearchResponse implements Parcelable {
+ /** @hide */
+ @IntDef(prefix = {"SEARCH_STATUS_"},
+ value = {SEARCH_STATUS_UNKNOWN,
+ SEARCH_STATUS_OK,
+ SEARCH_STATUS_TIME_OUT,
+ SEARCH_STATUS_NO_INTERNET})
+ public @interface SearchStatusCode {}
+ public static final int SEARCH_STATUS_UNKNOWN = -1;
+ public static final int SEARCH_STATUS_OK = 0;
+ public static final int SEARCH_STATUS_TIME_OUT = 1;
+ public static final int SEARCH_STATUS_NO_INTERNET = 2;
+
+ private final int mStatusCode;
+
+ /** Auto set by system servier, and the provider cannot set it. */
+ @NonNull
+ private String mSource;
+
+ @NonNull
+ private final List<SearchResult> mSearchResults;
+
+ private SearchResponse(Parcel in) {
+ this.mStatusCode = in.readInt();
+ this.mSource = in.readString();
+ this.mSearchResults = in.createTypedArrayList(SearchResult.CREATOR);
+ }
+
+ private SearchResponse(@SearchStatusCode int statusCode, String source,
+ List<SearchResult> searchResults) {
+ mStatusCode = statusCode;
+ mSource = source;
+ mSearchResults = searchResults;
+ }
+
+ /** Gets the search status code. */
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+
+ /** Gets the search provider package name. */
+ @NonNull
+ public String getSource() {
+ return mSource;
+ }
+
+ /** Gets the search results, which can be empty. */
+ @NonNull
+ public List<SearchResult> getSearchResults() {
+ return mSearchResults;
+ }
+
+ /** Sets the search provider, and this will be set by the system server.
+ *
+ * @hide
+ */
+ public void setSource(@NonNull String source) {
+ this.mSource = source;
+ }
+
+ private SearchResponse(Builder b) {
+ mStatusCode = b.mStatusCode;
+ mSource = requireNonNull(b.mSource);
+ mSearchResults = requireNonNull(b.mSearchResults);
+ }
+
+ /**
+ *
+ * @see Creator
+ *
+ */
+ @NonNull public static final Creator<SearchResponse> CREATOR = new Creator<SearchResponse>() {
+ @Override
+ public SearchResponse createFromParcel(Parcel p) {
+ return new SearchResponse(p);
+ }
+
+ @Override
+ public SearchResponse[] newArray(int size) {
+ return new SearchResponse[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(this.mStatusCode);
+ dest.writeString(this.mSource);
+ dest.writeTypedList(this.mSearchResults);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ SearchResponse that = (SearchResponse) obj;
+ return mStatusCode == that.mStatusCode
+ && Objects.equals(mSource, that.mSource)
+ && Objects.equals(mSearchResults, that.mSearchResults);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatusCode, mSource, mSearchResults);
+ }
+
+ /**
+ * Builder constructing SearchResponse.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private int mStatusCode;
+ private String mSource;
+ private List<SearchResult> mSearchResults;
+
+ /**
+ *
+ * @param statusCode the search status code.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@SearchStatusCode int statusCode) {
+ mStatusCode = statusCode;
+
+ /** Init with a default value. */
+ mSource = "DEFAULT";
+
+ mSearchResults = new ArrayList<SearchResult>();
+ }
+
+ /** Sets the search status code. */
+ @NonNull
+ public Builder setStatusCode(@SearchStatusCode int statusCode) {
+ this.mStatusCode = statusCode;
+ return this;
+ }
+
+ /** Sets the search provider, and this will be set by the system server.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setSource(@NonNull String source) {
+ this.mSource = source;
+ return this;
+ }
+
+ /** Sets the search results. */
+ @NonNull
+ public Builder setSearchResults(@NonNull List<SearchResult> searchResults) {
+ this.mSearchResults = searchResults;
+ return this;
+ }
+
+ /** Builds a SearchResponse based-on the given parameters. */
+ @NonNull
+ public SearchResponse build() {
+ if (mStatusCode < SEARCH_STATUS_UNKNOWN || mStatusCode > SEARCH_STATUS_NO_INTERNET
+ || mSearchResults == null) {
+ throw new IllegalStateException("Please make sure all @NonNull args are assigned.");
+ }
+
+ return new SearchResponse(mStatusCode, mSource, mSearchResults);
+ }
+ }
+}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/app/cloudsearch/SearchResult.aidl
similarity index 72%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/android/app/cloudsearch/SearchResult.aidl
index 5206831..daebfbf 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/app/cloudsearch/SearchResult.aidl
@@ -1,11 +1,11 @@
-/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.app.cloudsearch;
-/**
- * @hide
- */
-parcelable SelectionContext;
+parcelable SearchResult;
\ No newline at end of file
diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java
new file mode 100644
index 0000000..060931b
--- /dev/null
+++ b/core/java/android/app/cloudsearch/SearchResult.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cloudsearch;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A {@link SearchResult} includes all the information for one result item.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SearchResult implements Parcelable {
+
+ /** Short content best describing the result item. */
+ @NonNull
+ private final String mTitle;
+
+ /** Matched contents in the result item. */
+ @NonNull
+ private final String mSnippet;
+
+ /** Ranking Score provided by the search provider. */
+ private final float mScore;
+
+ /**
+ * List of public static KEYS for Bundles in mExtraInfos.
+ * mExtraInfos contains various information specified for different data types.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = {"EXTRAINFO_"},
+ value = {EXTRAINFO_APP_DOMAIN_URL,
+ EXTRAINFO_APP_ICON,
+ EXTRAINFO_APP_DEVELOPER_NAME,
+ EXTRAINFO_APP_SIZE_BYTES,
+ EXTRAINFO_APP_STAR_RATING,
+ EXTRAINFO_APP_IARC,
+ EXTRAINFO_APP_REVIEW_COUNT,
+ EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER,
+ EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER,
+ EXTRAINFO_SHORT_DESCRIPTION,
+ EXTRAINFO_LONG_DESCRIPTION,
+ EXTRAINFO_SCREENSHOTS,
+ EXTRAINFO_APP_BADGES,
+ EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING,
+ EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING,
+ EXTRAINFO_WEB_URL,
+ EXTRAINFO_WEB_ICON})
+ public @interface SearchResultExtraInfoKey {}
+ /** This App developer website's domain URL, String value expected. */
+ public static final String EXTRAINFO_APP_DOMAIN_URL = "APP_DOMAIN_URL";
+ /** This App result's ICON URL, String value expected. */
+ public static final String EXTRAINFO_APP_ICON = "APP_ICON";
+ /** This App developer's name, String value expected. */
+ public static final String EXTRAINFO_APP_DEVELOPER_NAME = "APP_DEVELOPER_NAME";
+ /** This App's pkg size in bytes, Double value expected. */
+ public static final String EXTRAINFO_APP_SIZE_BYTES = "APP_SIZE_BYTES";
+ /** This App developer's name, Double value expected. */
+ public static final String EXTRAINFO_APP_STAR_RATING = "APP_STAR_RATING";
+ /** This App's IARC rating, String value expected.
+ * IARC (International Age Rating Coalition) is partnered globally with major
+ * content rating organizations to provide a centralized and one-stop-shop for
+ * rating content on a global scale.
+ */
+ public static final String EXTRAINFO_APP_IARC = "APP_IARC";
+ /** This App's review count, Double value expected. */
+ public static final String EXTRAINFO_APP_REVIEW_COUNT = "APP_REVIEW_COUNT";
+ /** If this App contains the Ads Disclaimer, Boolean value expected. */
+ public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER =
+ "APP_CONTAINS_ADS_DISCLAIMER";
+ /** If this App contains the IAP Disclaimer, Boolean value expected. */
+ public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER =
+ "APP_CONTAINS_IAP_DISCLAIMER";
+ /** This App's short description, String value expected. */
+ public static final String EXTRAINFO_SHORT_DESCRIPTION = "SHORT_DESCRIPTION";
+ /** This App's long description, String value expected. */
+ public static final String EXTRAINFO_LONG_DESCRIPTION = "LONG_DESCRIPTION";
+ /** This App's screenshots, List<ImageLoadingBundle> value expected. */
+ public static final String EXTRAINFO_SCREENSHOTS = "SCREENSHOTS";
+ /** Editor's choices for this App, ArrayList<String> value expected. */
+ public static final String EXTRAINFO_APP_BADGES = "APP_BADGES";
+ /** Pre-registration game's action button text, String value expected. */
+ @SuppressLint("IntentName")
+ public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "ACTION_BUTTON_TEXT";
+ /** Pre-registration game's action button image, ImageLoadingBundle value expected. */
+ @SuppressLint("IntentName")
+ public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "ACTION_BUTTON_IMAGE";
+ /** Web content's URL, String value expected. */
+ public static final String EXTRAINFO_WEB_URL = "WEB_URL";
+ /** Web content's domain icon URL, String value expected. */
+ public static final String EXTRAINFO_WEB_ICON = "WEB_ICON";
+
+ @NonNull
+ private Bundle mExtraInfos;
+
+ private SearchResult(Parcel in) {
+ this.mTitle = in.readString();
+ this.mSnippet = in.readString();
+ this.mScore = in.readFloat();
+ this.mExtraInfos = in.readBundle();
+ }
+
+ private SearchResult(String title, String snippet, float score, Bundle extraInfos) {
+ mTitle = title;
+ mSnippet = snippet;
+ mScore = score;
+ mExtraInfos = extraInfos;
+ }
+
+ /** Gets the search result title. */
+ @NonNull
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /** Gets the search result snippet. */
+ @NonNull
+ public String getSnippet() {
+ return mSnippet;
+ }
+
+ /** Gets the ranking score provided by the original search provider. */
+ public float getScore() {
+ return mScore;
+ }
+
+ /** Gets the extra information associated with the search result. */
+ @NonNull
+ public Bundle getExtraInfos() {
+ return mExtraInfos;
+ }
+
+ private SearchResult(Builder b) {
+ mTitle = requireNonNull(b.mTitle);
+ mSnippet = requireNonNull(b.mSnippet);
+ mScore = b.mScore;
+ mExtraInfos = requireNonNull(b.mExtraInfos);
+ }
+
+ /**
+ *
+ * @see Creator
+ *
+ */
+ @NonNull public static final Creator<SearchResult> CREATOR = new Creator<SearchResult>() {
+ @Override
+ public SearchResult createFromParcel(Parcel p) {
+ return new SearchResult(p);
+ }
+
+ @Override
+ public SearchResult[] newArray(int size) {
+ return new SearchResult[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(this.mTitle);
+ dest.writeString(this.mSnippet);
+ dest.writeFloat(this.mScore);
+ dest.writeBundle(this.mExtraInfos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ SearchResult that = (SearchResult) obj;
+ return Objects.equals(mTitle, that.mTitle)
+ && Objects.equals(mSnippet, that.mSnippet)
+ && mScore == that.mScore
+ && Objects.equals(mExtraInfos, that.mExtraInfos);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mSnippet, mScore, mExtraInfos);
+ }
+
+ /**
+ * Builder constructing SearchResult.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private String mTitle;
+ private String mSnippet;
+ private float mScore;
+ private Bundle mExtraInfos;
+
+ /**
+ *
+ * @param title the title to the search result.
+ * @param extraInfos the extra infos associated with the search result.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull String title, @NonNull Bundle extraInfos) {
+ mTitle = title;
+ mExtraInfos = extraInfos;
+
+ mSnippet = "";
+ mScore = 0;
+ }
+
+ /** Sets the title to the search result. */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ this.mTitle = title;
+ return this;
+ }
+
+ /** Sets the snippet to the search result. */
+ @NonNull
+ public Builder setSnippet(@NonNull String snippet) {
+ this.mSnippet = snippet;
+ return this;
+ }
+
+ /** Sets the ranking score to the search result. */
+ @NonNull
+ public Builder setScore(float score) {
+ this.mScore = score;
+ return this;
+ }
+
+ /** Adds extra information to the search result for rendering in the UI. */
+ @NonNull
+ public Builder setExtraInfos(@NonNull Bundle extraInfos) {
+ this.mExtraInfos = extraInfos;
+ return this;
+ }
+
+ /** Builds a SearchResult based-on the given parameters. */
+ @NonNull
+ public SearchResult build() {
+ if (mTitle == null || mExtraInfos == null || mSnippet == null) {
+ throw new IllegalStateException("Please make sure all required args are assigned.");
+ }
+
+ return new SearchResult(mTitle, mSnippet, mScore, mExtraInfos);
+ }
+ }
+}
diff --git a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
deleted file mode 100644
index 4c06333..0000000
--- a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 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 android.app.servertransaction;
-
-import android.annotation.Nullable;
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
-
-import java.util.Objects;
-
-/**
- * The request to update display adjustments for a rotated activity or window token.
- * @hide
- */
-public class FixedRotationAdjustmentsItem extends ClientTransactionItem {
-
- /** The token who may have {@link android.content.res.Resources}. */
- private IBinder mToken;
-
- /**
- * The adjustments for the display adjustments of resources. If it is null, the existing
- * rotation adjustments will be dropped to restore natural state.
- */
- private FixedRotationAdjustments mFixedRotationAdjustments;
-
- private FixedRotationAdjustmentsItem() {}
-
- /** Obtain an instance initialized with provided params. */
- public static FixedRotationAdjustmentsItem obtain(IBinder token,
- FixedRotationAdjustments fixedRotationAdjustments) {
- FixedRotationAdjustmentsItem instance =
- ObjectPool.obtain(FixedRotationAdjustmentsItem.class);
- if (instance == null) {
- instance = new FixedRotationAdjustmentsItem();
- }
- instance.mToken = token;
- instance.mFixedRotationAdjustments = fixedRotationAdjustments;
-
- return instance;
- }
-
- @Override
- public void execute(ClientTransactionHandler client, IBinder token,
- PendingTransactionActions pendingActions) {
- client.handleFixedRotationAdjustments(mToken, mFixedRotationAdjustments);
- }
-
- @Override
- public void recycle() {
- mToken = null;
- mFixedRotationAdjustments = null;
- ObjectPool.recycle(this);
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
- dest.writeTypedObject(mFixedRotationAdjustments, flags);
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- final FixedRotationAdjustmentsItem other = (FixedRotationAdjustmentsItem) o;
- return Objects.equals(mToken, other.mToken)
- && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + Objects.hashCode(mToken);
- result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
- return result;
- }
-
- private FixedRotationAdjustmentsItem(Parcel in) {
- mToken = in.readStrongBinder();
- mFixedRotationAdjustments = in.readTypedObject(FixedRotationAdjustments.CREATOR);
- }
-
- public static final Creator<FixedRotationAdjustmentsItem> CREATOR =
- new Creator<FixedRotationAdjustmentsItem>() {
- public FixedRotationAdjustmentsItem createFromParcel(Parcel in) {
- return new FixedRotationAdjustmentsItem(in);
- }
-
- public FixedRotationAdjustmentsItem[] newArray(int size) {
- return new FixedRotationAdjustmentsItem[size];
- }
- };
-}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 0a2503c..abf1058 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -38,7 +38,6 @@
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Trace;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -78,7 +77,6 @@
* optimization for quick look up of the interface so the field is ignored for comparison.
*/
private IActivityClientController mActivityClientController;
- private FixedRotationAdjustments mFixedRotationAdjustments;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -97,8 +95,7 @@
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken,
- mLaunchedFromBubble);
+ client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -121,8 +118,7 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
- IActivityClientController activityClientController,
- FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken,
+ IActivityClientController activityClientController, IBinder shareableActivityToken,
boolean launchedFromBubble) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
@@ -131,7 +127,7 @@
setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
- activityClientController, fixedRotationAdjustments, shareableActivityToken,
+ activityClientController, shareableActivityToken,
launchedFromBubble);
return instance;
@@ -140,7 +136,7 @@
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, null, false);
+ null, false, null, null, null, null, false);
ObjectPool.recycle(this);
}
@@ -168,7 +164,6 @@
dest.writeTypedObject(mProfilerInfo, flags);
dest.writeStrongBinder(mAssistToken);
dest.writeStrongInterface(mActivityClientController);
- dest.writeTypedObject(mFixedRotationAdjustments, flags);
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
}
@@ -188,7 +183,7 @@
in.readTypedObject(ProfilerInfo.CREATOR),
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
- in.readTypedObject(FixedRotationAdjustments.CREATOR), in.readStrongBinder(),
+ in.readStrongBinder(),
in.readBoolean());
}
@@ -232,7 +227,6 @@
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
- && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments)
&& Objects.equals(mShareableActivityToken, other.mShareableActivityToken);
}
@@ -254,7 +248,6 @@
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
- result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
result = 31 * result + Objects.hashCode(mShareableActivityToken);
return result;
}
@@ -292,7 +285,6 @@
+ ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions
+ ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken
- + ",rotationAdj=" + mFixedRotationAdjustments
+ ",shareableActivityToken=" + mShareableActivityToken + "}";
}
@@ -304,8 +296,7 @@
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
IBinder assistToken, IActivityClientController activityClientController,
- FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken,
- boolean launchedFromBubble) {
+ IBinder shareableActivityToken, boolean launchedFromBubble) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -324,7 +315,6 @@
instance.mProfilerInfo = profilerInfo;
instance.mAssistToken = assistToken;
instance.mActivityClientController = activityClientController;
- instance.mFixedRotationAdjustments = fixedRotationAdjustments;
instance.mShareableActivityToken = shareableActivityToken;
instance.mLaunchedFromBubble = launchedFromBubble;
}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/app/usage/BroadcastResponseStats.aidl
similarity index 79%
rename from core/java/android/view/selectiontoolbar/SelectionContext.aidl
rename to core/java/android/app/usage/BroadcastResponseStats.aidl
index 5206831..5992841 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/app/usage/BroadcastResponseStats.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.
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.app.usage;
-/**
- * @hide
- */
-parcelable SelectionContext;
+parcelable BroadcastResponseStats;
\ No newline at end of file
diff --git a/core/java/android/app/usage/BroadcastResponseStats.java b/core/java/android/app/usage/BroadcastResponseStats.java
new file mode 100644
index 0000000..5acc3dda
--- /dev/null
+++ b/core/java/android/app/usage/BroadcastResponseStats.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.BroadcastOptions;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class containing a collection of stats related to response events started from an app
+ * after receiving a broadcast.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BroadcastResponseStats implements Parcelable {
+ private final String mPackageName;
+ private int mBroadcastsDispatchedCount;
+ private int mNotificationsPostedCount;
+ private int mNotificationsUpdatedCount;
+ private int mNotificationsCancelledCount;
+
+ public BroadcastResponseStats(@NonNull String packageName) {
+ mPackageName = packageName;
+ }
+
+ private BroadcastResponseStats(@NonNull Parcel in) {
+ mPackageName = in.readString8();
+ mBroadcastsDispatchedCount = in.readInt();
+ mNotificationsPostedCount = in.readInt();
+ mNotificationsUpdatedCount = in.readInt();
+ mNotificationsCancelledCount = in.readInt();
+ }
+
+ /**
+ * @return the name of the package that the stats in this object correspond to.
+ */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the total number of broadcasts that were dispatched to the app by the caller.
+ *
+ * <b> Note that the returned count will only include the broadcasts that the caller explicitly
+ * requested to record using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the total number of broadcasts that were dispatched to the app.
+ */
+ @IntRange(from = 0)
+ public int getBroadcastsDispatchedCount() {
+ return mBroadcastsDispatchedCount;
+ }
+
+ /**
+ * Returns the total number of notifications posted by the app soon after receiving a
+ * broadcast.
+ *
+ * <b> Note that the returned count will only include the notifications that correspond to the
+ * broadcasts that the caller explicitly requested to record using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the total number of notifications posted by the app soon after receiving
+ * a broadcast.
+ */
+ @IntRange(from = 0)
+ public int getNotificationsPostedCount() {
+ return mNotificationsPostedCount;
+ }
+
+ /**
+ * Returns the total number of notifications updated by the app soon after receiving a
+ * broadcast.
+ *
+ * <b> Note that the returned count will only include the notifications that correspond to the
+ * broadcasts that the caller explicitly requested to record using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the total number of notifications updated by the app soon after receiving
+ * a broadcast.
+ */
+ @IntRange(from = 0)
+ public int getNotificationsUpdatedCount() {
+ return mNotificationsUpdatedCount;
+ }
+
+ /**
+ * Returns the total number of notifications cancelled by the app soon after receiving a
+ * broadcast.
+ *
+ * <b> Note that the returned count will only include the notifications that correspond to the
+ * broadcasts that the caller explicitly requested to record using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the total number of notifications cancelled by the app soon after receiving
+ * a broadcast.
+ */
+ @IntRange(from = 0)
+ public int getNotificationsCancelledCount() {
+ return mNotificationsCancelledCount;
+ }
+
+ /** @hide */
+ public void incrementBroadcastsDispatchedCount(@IntRange(from = 0) int count) {
+ mBroadcastsDispatchedCount += count;
+ }
+
+ /** @hide */
+ public void incrementNotificationsPostedCount(@IntRange(from = 0) int count) {
+ mNotificationsPostedCount += count;
+ }
+
+ /** @hide */
+ public void incrementNotificationsUpdatedCount(@IntRange(from = 0) int count) {
+ mNotificationsUpdatedCount += count;
+ }
+
+ /** @hide */
+ public void incrementNotificationsCancelledCount(@IntRange(from = 0) int count) {
+ mNotificationsCancelledCount += count;
+ }
+
+ /** @hide */
+ public void addCounts(@NonNull BroadcastResponseStats stats) {
+ incrementBroadcastsDispatchedCount(stats.getBroadcastsDispatchedCount());
+ incrementNotificationsPostedCount(stats.getNotificationsPostedCount());
+ incrementNotificationsUpdatedCount(stats.getNotificationsUpdatedCount());
+ incrementNotificationsCancelledCount(stats.getNotificationsCancelledCount());
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "stats {"
+ + "broadcastsSent=" + mBroadcastsDispatchedCount
+ + ",notificationsPosted=" + mNotificationsPostedCount
+ + ",notificationsUpdated=" + mNotificationsUpdatedCount
+ + ",notificationsCancelled=" + mNotificationsCancelledCount
+ + "}";
+ }
+
+ @Override
+ public @ContentsFlags int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+ dest.writeString8(mPackageName);
+ dest.writeInt(mBroadcastsDispatchedCount);
+ dest.writeInt(mNotificationsPostedCount);
+ dest.writeInt(mNotificationsUpdatedCount);
+ dest.writeInt(mNotificationsCancelledCount);
+ }
+
+ public static final @NonNull Creator<BroadcastResponseStats> CREATOR =
+ new Creator<BroadcastResponseStats>() {
+ @Override
+ public @NonNull BroadcastResponseStats createFromParcel(@NonNull Parcel source) {
+ return new BroadcastResponseStats(source);
+ }
+
+ @Override
+ public @NonNull BroadcastResponseStats[] newArray(int size) {
+ return new BroadcastResponseStats[size];
+ }
+ };
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 170d766..6f8fea1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -17,6 +17,7 @@
package android.app.usage;
import android.app.PendingIntent;
+import android.app.usage.BroadcastResponseStats;
import android.app.usage.UsageEvents;
import android.content.pm.ParceledListSlice;
@@ -71,4 +72,10 @@
int getUsageSource();
void forceUsageSourceSettingRead();
long getLastTimeAnyComponentUsed(String packageName, String callingPackage);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
+ BroadcastResponseStats queryBroadcastResponseStats(
+ String packageName, long id, String callingPackage, int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
+ void clearBroadcastResponseStats(String packageName, long id, String callingPackage,
+ int userId);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d7e197e..b81c62d 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -18,6 +18,7 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -26,6 +27,7 @@
import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.app.Activity;
+import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -320,6 +322,17 @@
* @hide
*/
public static final int REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY = 1 << 2;
+ /**
+ * The app was moved to restricted bucket due to user interaction, i.e., toggling FAS.
+ *
+ * <p>
+ * Note: This should be coming from the more end-user facing UX, not from developer
+ * options nor adb command.
+ </p>
+ *
+ * @hide
+ */
+ public static final int REASON_SUB_FORCED_USER_FLAG_INTERACTION = 1 << 1;
/** @hide */
@@ -336,14 +349,15 @@
public @interface StandbyBuckets {}
/** @hide */
- @IntDef(flag = true, prefix = {"REASON_SUB_FORCED_SYSTEM_FLAG_FLAG_"}, value = {
+ @IntDef(flag = true, prefix = {"REASON_SUB_FORCED_"}, value = {
REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+ REASON_SUB_FORCED_USER_FLAG_INTERACTION,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface SystemForcedReasons {
+ public @interface ForcedReasons {
}
/**
@@ -1188,11 +1202,6 @@
case REASON_MAIN_FORCED_BY_USER:
sb.append("f");
if (subReason > 0) {
- // Although not expected and shouldn't happen, this could potentially have a
- // sub-reason if the system tries to give a reason when applying the
- // FORCED_BY_USER reason. The sub-reason is undefined (though most likely a
- // REASON_SUB_FORCED_SYSTEM_FLAG_ sub-reason), but it's better to note it in the
- // log than to exclude it altogether.
sb.append("-").append(Integer.toBinaryString(subReason));
}
break;
@@ -1383,4 +1392,65 @@
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the broadcast response stats since the last boot corresponding to
+ * {@code packageName} and {@code id}.
+ *
+ * <p>Broadcast response stats will include the aggregated data of what actions an app took upon
+ * receiving a broadcast. This data will consider the broadcasts that the caller sent to
+ * {@code packageName} and explicitly requested to record the response events using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @param packageName The name of the package that the caller wants to query for.
+ * @param id The ID corresponding to the broadcasts that the caller wants to query for. This is
+ * the ID the caller specifies when requesting a broadcast response event to be
+ * recorded using {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the broadcast response stats corresponding to {@code packageName} and {@code id}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
+ @UserHandleAware
+ @NonNull
+ public BroadcastResponseStats queryBroadcastResponseStats(
+ @NonNull String packageName, @IntRange(from = 1) long id) {
+ try {
+ return mService.queryBroadcastResponseStats(packageName, id,
+ mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears the broadcast response stats corresponding to {@code packageName} and {@code id}.
+ *
+ * When a caller uses this API, stats related to the events occurring till that point will be
+ * cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will
+ * return stats related to events occurring after this.
+ *
+ * @param packageName The name of the package that the caller wants to clear the data for.
+ * @param id The ID corresponding to the broadcasts that the caller wants to clear the data for.
+ * This is the ID the caller specifies when requesting a broadcast response event
+ * to be recorded using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @see #queryBroadcastResponseStats(String, long)
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
+ @UserHandleAware
+ public void clearBroadcastResponseStats(@NonNull String packageName,
+ @IntRange(from = 1) long id) {
+ try {
+ mService.clearBroadcastResponseStats(packageName, id,
+ mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 1d2f06d..bd8ba9e 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,7 +27,6 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
-import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -38,6 +37,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DataClass;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -88,10 +89,8 @@
* request to be associated with such devices.
*
* @see AssociationRequest.Builder#setDeviceProfile
- * @hide
*/
@RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING)
- @SystemApi
public static final String DEVICE_PROFILE_APP_STREAMING =
"android.app.role.COMPANION_DEVICE_APP_STREAMING";
@@ -103,15 +102,29 @@
* allowed to request to be associated with such devices.
*
* @see AssociationRequest.Builder#setDeviceProfile
- * @hide
*/
@RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION)
- @SystemApi
public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION =
"android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
+ /**
+ * Device profile: Allows the companion app to access notification, recent photos and media for
+ * computer cross-device features.
+ *
+ * Only applications that have been granted
+ * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_COMPUTER} are allowed to
+ * request to be associated with such devices.
+ *
+ * @see AssociationRequest.Builder#setDeviceProfile
+ */
+ @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER)
+ public static final String DEVICE_PROFILE_COMPUTER =
+ "android.app.role.COMPANION_DEVICE_COMPUTER";
+
/** @hide */
- @StringDef(value = { DEVICE_PROFILE_WATCH })
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(value = { DEVICE_PROFILE_WATCH, DEVICE_PROFILE_COMPUTER,
+ DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, DEVICE_PROFILE_APP_STREAMING })
public @interface DeviceProfile {}
/**
@@ -241,10 +254,7 @@
* Whether the association is to be managed by the companion application.
*
* @see Builder#setSelfManaged(boolean)
- * @hide
*/
- @SystemApi
- @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
public boolean isSelfManaged() {
return mSelfManaged;
}
@@ -255,10 +265,7 @@
* required.
*
* @see Builder#setForceConfirmation(boolean)
- * @hide
*/
- @SystemApi
- @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
public boolean isForceConfirmation() {
return mForceConfirmation;
}
@@ -374,9 +381,7 @@
* Requests for creating "self-managed" association MUST provide a Display name.
*
* @see #setDisplayName(CharSequence)
- * @hide
*/
- @SystemApi
@RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
@NonNull
public Builder setSelfManaged(boolean selfManaged) {
@@ -389,10 +394,7 @@
* Indicates whether the application would prefer the CompanionDeviceManager to collect an
* explicit confirmation from the user before creating an association, even if such
* confirmation is not required.
- *
- * @hide
*/
- @SystemApi
@RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
@NonNull
public Builder setForceConfirmation(boolean forceConfirmation) {
@@ -639,10 +641,10 @@
};
@DataClass.Generated(
- time = 1638962248060L,
+ time = 1643238443303L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
- inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
+ inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ae13425..36802eabe 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
import android.annotation.NonNull;
@@ -264,6 +265,7 @@
@UserHandleAware
@RequiresPermission(anyOf = {
REQUEST_COMPANION_PROFILE_WATCH,
+ REQUEST_COMPANION_PROFILE_COMPUTER,
REQUEST_COMPANION_PROFILE_APP_STREAMING,
REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION,
}, conditional = true)
@@ -318,6 +320,7 @@
@UserHandleAware
@RequiresPermission(anyOf = {
REQUEST_COMPANION_PROFILE_WATCH,
+ REQUEST_COMPANION_PROFILE_COMPUTER,
REQUEST_COMPANION_PROFILE_APP_STREAMING,
REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION
}, conditional = true)
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 610b7ee..cb96ebe 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -36,11 +36,6 @@
* See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}.
*
* <p>
- * Additionally, the service will receive a call from the system, if and when the system needs to
- * transfer data to the companion device.
- * See {@link #dispatchMessage(int, int, byte[])}).
- *
- * <p>
* Companion applications must create a service that {@code extends}
* {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the
* "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission
@@ -79,8 +74,8 @@
* <p>
* It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
* In such case, the system will bind all declared services, but will deliver
- * {@link #onDeviceAppeared(AssociationInfo)}, {@link #onDeviceDisappeared(AssociationInfo)} and
- * {@link #dispatchMessage(int, int, byte[])} only to one "primary" services.
+ * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
+ * only to one "primary" services.
* Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
* service using "android.companion.primary" tag.
* <pre>{@code
@@ -156,6 +151,8 @@
* @param messageId system assigned id of the message to be sent
* @param associationId association id of the associated device
* @param message message to be sent
+ *
+ * @hide
*/
@MainThread
public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
@@ -172,6 +169,8 @@
* @param messageId id of the message
* @param associationId id of the associated device
* @param message messaged received from the associated device
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ce2efcf..4b4e008 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4979,6 +4979,20 @@
public static final String SMARTSPACE_SERVICE = "smartspace";
/**
+ * Used for getting the cloudsearch service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(CLOUDSEARCH_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ // TODO(216507592): Change cloudsearch_service to cloudsearch.
+ @SystemApi
+ @SuppressLint("ServiceName")
+ public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
@@ -5093,6 +5107,16 @@
public static final String DROPBOX_SERVICE = "dropbox";
/**
+ * System service name for BinaryTransparencyService. This is used to retrieve measurements
+ * pertaining to various pre-installed and system binaries on device for the purposes of
+ * providing transparency to the user.
+ *
+ * @hide
+ */
+ @SuppressLint("ServiceName")
+ public static final String BINARY_TRANSPARENCY_SERVICE = "transparency";
+
+ /**
* System service name for the DeviceIdleManager.
* @see #getSystemService(String)
* @hide
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7f00bcb..fb186fd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2385,6 +2385,10 @@
* {@link android.Manifest.permission#START_VIEW_APP_FEATURES} permission to ensure that
* only the system can launch this activity. The system will not launch activities
* that are not properly protected.
+ *
+ * An optional <meta-data> tag in the activity's manifest with
+ * android:name=app_features_preference_summary and android:resource=@string/<string name> will
+ * be used to add a summary line for the "All Services" preference in settings.
* </p>
* @hide
*/
@@ -3797,47 +3801,6 @@
"android.intent.action.ACTION_IDLE_MAINTENANCE_END";
/**
- * Broadcast Action: A broadcast sent by the system to indicate that
- * {@link android.safetycenter.SafetyCenterManager} is requesting data from safety sources
- * regarding their safety state.
- *
- * This broadcast is sent when a user triggers a data refresh from the Safety Center UI or when
- * Safety Center detects that its stored safety information is stale and needs to be updated.
- *
- * This broadcast is sent explicitly to safety sources by targeting intents to a specified set
- * of components provided by the safety sources in the safety source configuration.
- * The receiving components should be manifest-declared receivers so that safety sources can be
- * requested to send data even if they are not running.
- *
- * On receiving this broadcast, safety sources should determine their safety state
- * according to the parameters specified in the intent extras (see below) and send Safety Center
- * data about their safety state using
- * {@link android.safetycenter.SafetyCenterManager#sendSafetyCenterUpdate(android.safetycenter.SafetySourceData)}.
- *
- * <p class="note">This is a protected intent that can only be sent by the system.
- *
- * <p>Includes the following extras:
- * <ul>
- * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}: An int representing the type of data
- * being requested. Possible values are all values in {@link RefreshRequestType}.
- * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS}: A {@code String[]} of ids
- * representing the safety sources being requested for data. This extra exists for
- * disambiguation in the case that a single component is responsible for receiving refresh
- * requests for multiple safety sources.
- * </ul>
- *
- * @hide
- */
- // TODO(b/210805082): Define the term "safety sources" more concretely here once safety sources
- // are configured in xml config.
- // TODO(b/210979035): Determine recommendation for sources if they are requested for fresh data
- // but cannot provide it.
- @SystemApi
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_REFRESH_SAFETY_SOURCES =
- "android.intent.action.REFRESH_SAFETY_SOURCES";
-
- /**
* Broadcast Action: a remote intent is to be broadcasted.
*
* A remote intent is used for remote RPC between devices. The remote intent
@@ -5053,6 +5016,17 @@
public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION =
"android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION";
+ /**
+ * Broadcast Action: Start the foreground service manager.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER =
+ "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER";
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
@@ -6461,77 +6435,6 @@
public static final String EXTRA_VISIBILITY_ALLOW_LIST =
"android.intent.extra.VISIBILITY_ALLOW_LIST";
-
- /**
- * Used as a {@code String[]} extra field in
- * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} intents to specify the safety
- * source ids of the safety sources being requested for data by Safety Center.
- *
- * When this extra field is not specified in the intent, it is assumed that Safety Center is
- * requesting data from all safety sources supported by the component receiving the broadcast.
- * @hide
- */
- @SystemApi
- public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS =
- "android.intent.extra.REFRESH_SAFETY_SOURCE_IDS";
-
- /**
- * Used as an {@code int} extra field in
- * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} intents to specify the type of
- * data request from Safety Center.
- *
- * Possible values are all values in {@link RefreshRequestType}.
- *
- * @hide
- */
- @SystemApi
- public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE =
- "android.intent.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE";
-
- /**
- * All possible types of data refresh requests in broadcasts with intent action
- * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}.
- *
- * @hide
- */
- @IntDef(prefix = { "EXTRA_REFRESH_REQUEST_TYPE_" }, value = {
- EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA,
- EXTRA_REFRESH_REQUEST_TYPE_GET_DATA,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RefreshRequestType {}
-
- /**
- * Used as an int value for
- * {@link android.content.Intent#EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}
- * to indicate that the safety source should fetch fresh data relating to their safety state
- * upon receiving a broadcast with intent action
- * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} and provide it to Safety Center.
- *
- * The term "fresh" here means that the sources should ensure that the safety data is accurate
- * as possible at the time of providing it to Safety Center, even if it involves performing an
- * expensive and/or slow process.
- *
- * @hide
- */
- @SystemApi
- public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0;
-
- /**
- * Used as an int value for
- * {@link android.content.Intent#EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}
- * to indicate that upon receiving a broadcasts with intent action
- * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}, the safety source should
- * provide data relating to their safety state to Safety Center.
- *
- * If the source already has its safety data cached, it may provide it without triggering a
- * process to fetch state which may be expensive and/or slow.
- *
- * @hide
- */
- @SystemApi
- public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1;
-
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5b0c275..e5c31d7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3154,6 +3154,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports exposing head tracker sensors from peripheral
+ * devices via the dynamic sensors API.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_DYNAMIC_HEAD_TRACKER = "android.hardware.sensor.dynamic.head_tracker";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports high fidelity sensor processing
* capabilities.
*/
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7fc242c..88d7004 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -170,6 +170,15 @@
public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 1 << 7;
/**
+ * The number of foreground service types, this doesn't include
+ * the {@link #FOREGROUND_SERVICE_TYPE_MANIFEST} and {@link #FOREGROUND_SERVICE_TYPE_NONE}
+ * as they're not real service types.
+ *
+ * @hide
+ */
+ public static final int NUM_OF_FOREGROUND_SERVICE_TYPES = 8;
+
+ /**
* A special value indicates to use all types set in manifest file.
*/
public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1;
@@ -239,6 +248,38 @@
+ " " + name + "}";
}
+ /**
+ * @return The label for the given foreground service type.
+ *
+ * @hide
+ */
+ public static String foregroundServiceTypeToLabel(@ForegroundServiceType int type) {
+ switch (type) {
+ case FOREGROUND_SERVICE_TYPE_MANIFEST:
+ return "manifest";
+ case FOREGROUND_SERVICE_TYPE_NONE:
+ return "none";
+ case FOREGROUND_SERVICE_TYPE_DATA_SYNC:
+ return "dataSync";
+ case FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK:
+ return "mediaPlayback";
+ case FOREGROUND_SERVICE_TYPE_PHONE_CALL:
+ return "phoneCall";
+ case FOREGROUND_SERVICE_TYPE_LOCATION:
+ return "location";
+ case FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE:
+ return "connectedDevice";
+ case FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION:
+ return "mediaProjection";
+ case FOREGROUND_SERVICE_TYPE_CAMERA:
+ return "camera";
+ case FOREGROUND_SERVICE_TYPE_MICROPHONE:
+ return "microphone";
+ default:
+ return "unknown";
+ }
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 5b727cc..5031faa 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -198,7 +198,7 @@
// Prune the cache before adding new items.
final int N = sCache.size();
for (int i = N - 1; i >= 0; i--) {
- if (sCache.valueAt(i).get() == null) {
+ if (sCache.valueAt(i).refersTo(null)) {
sCache.removeAt(i);
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cb53a2a..5fd0d84 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -83,7 +83,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -143,9 +142,6 @@
@UnsupportedAppUsage
private DrawableInflater mDrawableInflater;
- /** Used to override the returned adjustments of {@link #getDisplayAdjustments}. */
- private DisplayAdjustments mOverrideDisplayAdjustments;
-
/** Lock object used to protect access to {@link #mTmpValue}. */
private final Object mTmpValueLock = new Object();
@@ -2080,7 +2076,7 @@
// Clean up references to garbage collected themes
if (mThemeRefs.size() > mThemeRefsNextFlushSize) {
- mThemeRefs.removeIf(ref -> ref.get() == null);
+ mThemeRefs.removeIf(ref -> ref.refersTo(null));
mThemeRefsNextFlushSize = Math.max(MIN_THEME_REFS_FLUSH_SIZE,
2 * mThemeRefs.size());
}
@@ -2174,38 +2170,15 @@
/** @hide */
@UnsupportedAppUsage(trackingBug = 176190631)
public DisplayAdjustments getDisplayAdjustments() {
- final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
- if (overrideDisplayAdjustments != null) {
- return overrideDisplayAdjustments;
- }
return mResourcesImpl.getDisplayAdjustments();
}
/**
- * Customize the display adjustments based on the current one in {@link #mResourcesImpl}, in
- * order to isolate the effect with other instances of {@link Resource} that may share the same
- * instance of {@link ResourcesImpl}.
- *
- * @param override The operation to override the existing display adjustments. If it is null,
- * the override adjustments will be cleared.
- * @hide
- */
- public void overrideDisplayAdjustments(@Nullable Consumer<DisplayAdjustments> override) {
- if (override != null) {
- mOverrideDisplayAdjustments = new DisplayAdjustments(
- mResourcesImpl.getDisplayAdjustments());
- override.accept(mOverrideDisplayAdjustments);
- } else {
- mOverrideDisplayAdjustments = null;
- }
- }
-
- /**
* Return {@code true} if the override display adjustments have been set.
* @hide
*/
public boolean hasOverrideDisplayAdjustments() {
- return mOverrideDisplayAdjustments != null;
+ return false;
}
/**
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index c308400..cf6e166 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -257,7 +257,7 @@
for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
- if (key.get() == null) {
+ if (key.refersTo(null)) {
mChangeCallbacks.removeAt(i);
} else {
uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index acceb654..515a009 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -156,7 +156,7 @@
* used as USAGE_GPU_COLOR_OUTPUT the buffer will behave similar to a single-buffered window.
* When used with USAGE_COMPOSER_OVERLAY, the system will try to prioritize the buffer
* receiving an overlay plane & avoid caching it in intermediate composition buffers. */
- public static final long USAGE_FRONT_BUFFER = 1 << 32;
+ public static final long USAGE_FRONT_BUFFER = 1L << 32;
/**
* Creates a new <code>HardwareBuffer</code> instance.
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 7074a2c..79153d7 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -31,6 +31,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
+import android.service.SensorPrivacySensorProto;
import android.service.SensorPrivacyToggleSourceProto;
import android.util.ArrayMap;
import android.util.Log;
@@ -75,7 +76,7 @@
private final SparseArray<Boolean> mToggleSupportCache = new SparseArray<>();
/**
- * Individual sensors not listed in {@link Sensors}
+ * Sensor constants which are used in {@link SensorPrivacyManager}
*/
public static class Sensors {
@@ -84,12 +85,12 @@
/**
* Constant for the microphone
*/
- public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
+ public static final int MICROPHONE = SensorPrivacySensorProto.MICROPHONE;
/**
* Constant for the camera
*/
- public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA;
+ public static final int CAMERA = SensorPrivacySensorProto.CAMERA;
/**
* Individual sensors not listed in {@link Sensors}
@@ -161,6 +162,68 @@
}
/**
+ * Types of toggles which can exist for sensor privacy
+ * @hide
+ */
+ public static class ToggleTypes {
+ private ToggleTypes() {}
+
+ /**
+ * Constant for software toggle.
+ */
+ public static final int SOFTWARE = SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
+
+ /**
+ * Constant for hardware toggle.
+ */
+ public static final int HARDWARE = SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
+
+ /**
+ * Types of toggles which can exist for sensor privacy
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ SOFTWARE,
+ HARDWARE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToggleType {}
+
+ }
+
+ /**
+ * Types of state which can exist for the sensor privacy toggle
+ * @hide
+ */
+ public static class StateTypes {
+ private StateTypes() {}
+
+ /**
+ * Constant indicating privacy is enabled.
+ */
+ public static final int ENABLED = SensorPrivacyIndividualEnabledSensorProto.ENABLED;
+
+ /**
+ * Constant indicating privacy is disabled.
+ */
+ public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
+
+ /**
+ * Types of state which can exist for a sensor privacy toggle
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ ENABLED,
+ DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StateType {}
+
+ }
+
+ /**
* A class implementing this interface can register with the {@link
* android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
* state changes.
@@ -507,7 +570,6 @@
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
- * @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
*
* @hide
@@ -521,7 +583,6 @@
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
- * @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
* @param userId the user's id
*
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index ad6c12b..cc9aeab 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -20,6 +20,7 @@
import android.graphics.PointF;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
+import android.view.InputChannel;
import android.view.InputEvent;
import java.util.List;
@@ -123,4 +124,13 @@
*/
void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
}
+
+ /** Create an {@link InputChannel} that is registered to InputDispatcher. */
+ public abstract InputChannel createInputChannel(String inputChannelName);
+
+ /**
+ * Pilfer pointers from the input channel with the given token so that ongoing gestures are
+ * canceled for all other channels.
+ */
+ public abstract void pilferPointers(IBinder token);
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index cc325cd..41642e7 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -78,6 +78,7 @@
private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
private static final int DO_CAN_START_STYLUS_HANDWRITING = 100;
private static final int DO_START_STYLUS_HANDWRITING = 110;
+ private static final int DO_INIT_INK_WINDOW = 120;
final WeakReference<InputMethodServiceInternal> mTarget;
final Context mContext;
@@ -245,11 +246,15 @@
}
case DO_START_STYLUS_HANDWRITING: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.startStylusHandwriting((InputChannel) args.arg1,
+ inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
(List<MotionEvent>) args.arg2);
args.recycle();
return;
}
+ case DO_INIT_INK_WINDOW: {
+ inputMethod.initInkWindow();
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
@@ -393,10 +398,17 @@
@BinderThread
@Override
- public void startStylusHandwriting(@NonNull InputChannel channel,
+ public void startStylusHandwriting(int requestId, @NonNull InputChannel channel,
@Nullable List<MotionEvent> stylusEvents)
throws RemoteException {
mCaller.executeOrSendMessage(
- mCaller.obtainMessageOO(DO_START_STYLUS_HANDWRITING, channel, stylusEvents));
+ mCaller.obtainMessageIOO(DO_START_STYLUS_HANDWRITING, requestId, channel,
+ stylusEvents));
+ }
+
+ @BinderThread
+ @Override
+ public void initInkWindow() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_INIT_INK_WINDOW));
}
}
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index e11d635..83b053a 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -39,6 +39,7 @@
final class InkWindow extends PhoneWindow {
private final WindowManager mWindowManager;
+ private boolean mIsViewAdded;
public InkWindow(@NonNull Context context) {
super(context);
@@ -47,6 +48,7 @@
final LayoutParams attrs = getAttributes();
attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0);
+ // TODO(b/210039666): use INPUT_FEATURE_NO_INPUT_CHANNEL once b/216179339 is fixed.
setAttributes(attrs);
// Ink window is not touchable with finger.
addFlags(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCHABLE
@@ -57,16 +59,30 @@
}
/**
+ * Initialize InkWindow if we only want to create and draw surface but not show it.
+ */
+ void initOnly() {
+ show(true /* keepInvisible */);
+ }
+
+ /**
* Method to show InkWindow on screen.
* Emulates internal behavior similar to Dialog.show().
*/
void show() {
+ show(false /* keepInvisible */);
+ }
+
+ private void show(boolean keepInvisible) {
if (getDecorView() == null) {
Slog.i(InputMethodService.TAG, "DecorView is not set for InkWindow. show() failed.");
return;
}
- getDecorView().setVisibility(View.VISIBLE);
- mWindowManager.addView(getDecorView(), getAttributes());
+ getDecorView().setVisibility(keepInvisible ? View.INVISIBLE : View.VISIBLE);
+ if (!mIsViewAdded) {
+ mWindowManager.addView(getDecorView(), getAttributes());
+ mIsViewAdded = true;
+ }
}
/**
@@ -78,6 +94,7 @@
if (getDecorView() != null) {
getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE);
}
+ //TODO(b/210039666): remove window from WM after a delay. Delay amount TBD.
}
void setToken(@NonNull IBinder token) {
@@ -85,4 +102,11 @@
lp.token = token;
setAttributes(lp);
}
+
+ /**
+ * Returns {@code true} if Window was created and added to WM.
+ */
+ boolean isInitialized() {
+ return mIsViewAdded;
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 5d2d8ea..14f92fb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -82,6 +82,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -95,8 +96,11 @@
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
+import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
+import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
+import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -148,6 +152,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.OptionalInt;
/**
* InputMethodService provides a standard implementation of an InputMethod,
@@ -470,7 +475,7 @@
private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
@NonNull
- private final NavigationBarController mNavigationBarController =
+ final NavigationBarController mNavigationBarController =
new NavigationBarController(this);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -567,7 +572,8 @@
private boolean mAutomotiveHideNavBarForKeyboard;
private boolean mIsAutomotive;
- private boolean mHandwritingStarted;
+ private @NonNull OptionalInt mHandwritingRequestId = OptionalInt.empty();
+ private InputEventReceiver mHandwritingEventReceiver;
private Handler mHandler;
private boolean mImeSurfaceScheduledForRemoval;
private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
@@ -736,6 +742,10 @@
onUnbindInput();
mInputBinding = null;
mInputConnection = null;
+ // free-up cached InkWindow surface on detaching from current client.
+ if (mInkWindow != null) {
+ mInkWindow.hide(true /* remove */);
+ }
}
/**
@@ -888,7 +898,7 @@
@Override
public void canStartStylusHandwriting(int requestId) {
if (DEBUG) Log.v(TAG, "canStartStylusHandwriting()");
- if (mHandwritingStarted) {
+ if (mHandwritingRequestId.isPresent()) {
Log.d(TAG, "There is an ongoing Handwriting session. ignoring.");
return;
}
@@ -896,10 +906,15 @@
Log.d(TAG, "Input should have started before starting Stylus handwriting.");
return;
}
+ if (!mInkWindow.isInitialized()) {
+ // prepare hasn't been called by Stylus HOVER.
+ onPrepareStylusHandwriting();
+ }
if (onStartStylusHandwriting()) {
mPrivOps.onStylusHandwritingReady(requestId);
} else {
Log.i(TAG, "IME is not ready. Can't start Stylus Handwriting");
+ // TODO(b/210039666): see if it's valuable to propagate this back to IMM.
}
}
@@ -910,18 +925,44 @@
@MainThread
@Override
public void startStylusHandwriting(
- @NonNull InputChannel channel, @Nullable List<MotionEvent> stylusEvents) {
+ int requestId, @NonNull InputChannel channel,
+ @NonNull List<MotionEvent> stylusEvents) {
if (DEBUG) Log.v(TAG, "startStylusHandwriting()");
- if (mHandwritingStarted) {
+ Objects.requireNonNull(channel);
+ Objects.requireNonNull(stylusEvents);
+
+ if (mHandwritingRequestId.isPresent()) {
return;
}
- mHandwritingStarted = true;
+ mHandwritingRequestId = OptionalInt.of(requestId);
mShowInputRequested = false;
mInkWindow.show();
- // TODO: deliver previous @param stylusEvents
- // TODO: create spy receiver for @param channel
+
+ // deliver previous @param stylusEvents
+ stylusEvents.forEach(mInkWindow.getDecorView()::dispatchTouchEvent);
+ // create receiver for channel
+ mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
+ channel,
+ Looper.getMainLooper(), Choreographer.getInstance(),
+ event -> {
+ if (!(event instanceof MotionEvent)) {
+ return false;
+ }
+ return mInkWindow.getDecorView().dispatchTouchEvent((MotionEvent) event);
+ });
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public void initInkWindow() {
+ mInkWindow.initOnly();
+ onPrepareStylusHandwriting();
}
/**
@@ -2175,10 +2216,12 @@
}
/**
- * Called by the framework to create the layout for showing extacted text.
+ * Called by the framework to create the layout for showing extracted text.
* Only called when in fullscreen mode. The returned view hierarchy must
* have an {@link ExtractEditText} whose ID is
- * {@link android.R.id#inputExtractEditText}.
+ * {@link android.R.id#inputExtractEditText}, with action ID
+ * {@link android.R.id#inputExtractAction} and accessories ID
+ * {@link android.R.id#inputExtractAccessories}.
*/
public View onCreateExtractTextView() {
return mInflater.inflate(
@@ -2295,7 +2338,19 @@
}
}
}
-
+
+ /**
+ * Called to prepare stylus handwriting.
+ * The system calls this before the first {@link #onStartStylusHandwriting} request.
+ *
+ * <p>Note: The system tries to call this as early as possible, when it detects that
+ * handwriting stylus input is imminent. However, that a subsequent call to
+ * {@link #onStartStylusHandwriting} actually happens is not guaranteed.</p>
+ */
+ public void onPrepareStylusHandwriting() {
+ // Intentionally empty
+ }
+
/**
* Called when an app requests stylus handwriting
* {@link InputMethodManager#startStylusHandwriting(View)}.
@@ -2358,12 +2413,18 @@
if (mInkWindow == null) {
return;
}
- if (!mHandwritingStarted) {
+ if (!mHandwritingRequestId.isPresent()) {
return;
}
- mHandwritingStarted = false;
+ final int requestId = mHandwritingRequestId.getAsInt();
+ mHandwritingRequestId = OptionalInt.empty();
+
+ mHandwritingEventReceiver.dispose();
+ mHandwritingEventReceiver = null;
mInkWindow.hide(false /* remove */);
+
+ mPrivOps.finishStylusHandwriting(requestId);
onFinishStylusHandwriting();
}
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 7bc9573..2484cf0 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -17,7 +17,9 @@
package android.inputmethodservice;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.StatusBarManager;
@@ -40,6 +42,7 @@
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
+import android.view.WindowInsetsController.Appearance;
import android.view.WindowManagerPolicyConstants;
import android.widget.FrameLayout;
@@ -68,6 +71,9 @@
default void onDestroy() {
}
+ default void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ }
+
default String toDebugString() {
return "No-op implementation";
}
@@ -100,6 +106,10 @@
mImpl.onDestroy();
}
+ void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ mImpl.onSystemBarAppearanceChanged(appearance);
+ }
+
String toDebugString() {
return mImpl.toDebugString();
}
@@ -120,6 +130,9 @@
@Nullable
private BroadcastReceiver mSystemOverlayChangedReceiver;
+ @Appearance
+ private int mAppearance;
+
Impl(@NonNull InputMethodService inputMethodService) {
mService = inputMethodService;
}
@@ -187,6 +200,8 @@
}
mNavigationBarFrame.setBackground(null);
+
+ setIconTintInternal(calculateTargetDarkIntensity(mAppearance));
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -266,34 +281,50 @@
}
final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
if (zOrderChanged || insetChanged) {
- final NavigationBarFrame that = mNavigationBarFrame;
- that.post(() -> {
- if (!that.isAttachedToWindow()) {
- return;
- }
- final Insets currentSystemInsets = getSystemInsets();
- if (!Objects.equals(currentSystemInsets, mLastInsets)) {
- that.setLayoutParams(
- new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- currentSystemInsets.bottom, Gravity.BOTTOM));
- mLastInsets = currentSystemInsets;
- }
- if (decor instanceof ViewGroup) {
- ViewGroup decorGroup = (ViewGroup) decor;
- final View navbarBackgroundView =
- window.getNavigationBarBackgroundView();
- if (navbarBackgroundView != null
- && decorGroup.indexOfChild(navbarBackgroundView)
- > decorGroup.indexOfChild(that)) {
- decorGroup.bringChildToFront(that);
- }
- }
- });
+ scheduleRelayout();
}
}
}
+ private void scheduleRelayout() {
+ // Capture the current frame object in case the object is replaced or cleared later.
+ final NavigationBarFrame frame = mNavigationBarFrame;
+ frame.post(() -> {
+ if (mDestroyed) {
+ return;
+ }
+ if (!frame.isAttachedToWindow()) {
+ return;
+ }
+ final Window window = mService.mWindow.getWindow();
+ if (window == null) {
+ return;
+ }
+ final View decor = window.peekDecorView();
+ if (decor == null) {
+ return;
+ }
+ if (!(decor instanceof ViewGroup)) {
+ return;
+ }
+ final ViewGroup decorGroup = (ViewGroup) decor;
+ final Insets currentSystemInsets = getSystemInsets();
+ if (!Objects.equals(currentSystemInsets, mLastInsets)) {
+ frame.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ currentSystemInsets.bottom, Gravity.BOTTOM));
+ mLastInsets = currentSystemInsets;
+ }
+ final View navbarBackgroundView =
+ window.getNavigationBarBackgroundView();
+ if (navbarBackgroundView != null
+ && decorGroup.indexOfChild(navbarBackgroundView)
+ > decorGroup.indexOfChild(frame)) {
+ decorGroup.bringChildToFront(frame);
+ }
+ });
+ }
+
private boolean isGesturalNavigationEnabled() {
final Resources resources = mService.getResources();
if (resources == null) {
@@ -373,9 +404,41 @@
}
@Override
+ public void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ if (mDestroyed) {
+ return;
+ }
+
+ mAppearance = appearance;
+
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+
+ final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
+ setIconTintInternal(targetDarkIntensity);
+ }
+
+ private void setIconTintInternal(float darkIntensity) {
+ final NavigationBarView navigationBarView =
+ mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
+ if (navigationBarView == null) {
+ return;
+ }
+ navigationBarView.setDarkIntensity(darkIntensity);
+ }
+
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private static float calculateTargetDarkIntensity(@Appearance int appearance) {
+ final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+ return lightNavBar ? 1.0f : 0.0f;
+ }
+
+ @Override
public String toDebugString() {
return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ + " mAppearance=0x" + Integer.toHexString(mAppearance)
+ "}";
}
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 6c8eb41..0893d2a 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -23,7 +23,6 @@
import android.annotation.IntDef;
import android.app.Dialog;
-import android.content.Context;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -32,6 +31,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowInsetsController;
import android.view.WindowManager;
import java.lang.annotation.Retention;
@@ -47,6 +47,7 @@
private final KeyEvent.DispatcherState mDispatcherState;
private final Rect mBounds = new Rect();
+ private final InputMethodService mService;
@Retention(SOURCE)
@IntDef(value = {WindowState.TOKEN_PENDING, WindowState.TOKEN_SET,
@@ -120,7 +121,7 @@
/**
* Create a SoftInputWindow that uses a custom style.
*
- * @param context The Context in which the DockWindow should run. In
+ * @param service The {@link InputMethodService} in which the DockWindow should run. In
* particular, it uses the window manager and theme from this context
* to present its UI.
* @param theme A style resource describing the theme to use for the window.
@@ -129,8 +130,10 @@
* using styles. This theme is applied on top of the current theme in
* <var>context</var>. If 0, the default dialog theme will be used.
*/
- SoftInputWindow(Context context, int theme, KeyEvent.DispatcherState dispatcherState) {
- super(context, theme);
+ SoftInputWindow(InputMethodService service, int theme,
+ KeyEvent.DispatcherState dispatcherState) {
+ super(service, theme);
+ mService = service;
mDispatcherState = dispatcherState;
}
@@ -261,6 +264,11 @@
}
}
+ @Override
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance);
+ }
+
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mBounds.dumpDebug(proto, BOUNDS);
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 5554137..036607b 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -25,6 +25,12 @@
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
import static android.net.IpSecAlgorithm.CRYPT_AES_CTR;
+import static android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig;
+import static android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.Preconditions.checkStringNotEmpty;
@@ -34,6 +40,14 @@
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.content.pm.PackageManager;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.security.Credentials;
import android.util.Log;
@@ -644,6 +658,102 @@
return Objects.requireNonNull(reference, String.format(messageTemplate, messageArgs));
}
+ private static void checkBuilderSetter(boolean constructedFromIkeTunConParams,
+ @NonNull String message) {
+ if (constructedFromIkeTunConParams) {
+ throw new IllegalArgumentException("Constructed using IkeTunnelConnectionParams "
+ + "should not set " + message);
+ }
+ }
+
+ private static int getTypeFromIkeSession(@NonNull IkeSessionParams params) {
+ final IkeAuthConfig config = params.getLocalAuthConfig();
+ if (config instanceof IkeAuthDigitalSignLocalConfig) {
+ return TYPE_IKEV2_IPSEC_RSA;
+ } else if (config instanceof IkeAuthEapConfig) {
+ return TYPE_IKEV2_IPSEC_USER_PASS;
+ } else if (config instanceof IkeAuthPskConfig) {
+ return TYPE_IKEV2_IPSEC_PSK;
+ } else {
+ throw new IllegalStateException("Invalid local IkeAuthConfig");
+ }
+ }
+
+ @Nullable
+ private static String getPasswordFromIkeSession(@NonNull IkeSessionParams params) {
+ if (!(params.getLocalAuthConfig() instanceof IkeAuthEapConfig)) return null;
+
+ final IkeAuthEapConfig ikeAuthEapConfig = (IkeAuthEapConfig) params.getLocalAuthConfig();
+ final EapMsChapV2Config eapMsChapV2Config =
+ ikeAuthEapConfig.getEapConfig().getEapMsChapV2Config();
+ return (eapMsChapV2Config != null) ? eapMsChapV2Config.getPassword() : null;
+ }
+
+ @Nullable
+ private static String getUsernameFromIkeSession(@NonNull IkeSessionParams params) {
+ if (!(params.getLocalAuthConfig() instanceof IkeAuthEapConfig)) return null;
+
+ final IkeAuthEapConfig ikeAuthEapConfig = (IkeAuthEapConfig) params.getLocalAuthConfig();
+ final EapMsChapV2Config eapMsChapV2Config =
+ ikeAuthEapConfig.getEapConfig().getEapMsChapV2Config();
+ return (eapMsChapV2Config != null) ? eapMsChapV2Config.getUsername() : null;
+ }
+
+ @Nullable
+ private static X509Certificate getUserCertFromIkeSession(@NonNull IkeSessionParams params) {
+ if (!(params.getLocalAuthConfig() instanceof IkeAuthDigitalSignLocalConfig)) return null;
+
+ final IkeAuthDigitalSignLocalConfig config =
+ (IkeAuthDigitalSignLocalConfig) params.getLocalAuthConfig();
+ return config.getClientEndCertificate();
+ }
+
+ @Nullable
+ private static X509Certificate getServerRootCaCertFromIkeSession(
+ @NonNull IkeSessionParams params) {
+ if (!(params.getRemoteAuthConfig() instanceof IkeAuthDigitalSignRemoteConfig)) return null;
+
+ final IkeAuthDigitalSignRemoteConfig config =
+ (IkeAuthDigitalSignRemoteConfig) params.getRemoteAuthConfig();
+ return config.getRemoteCaCert();
+ }
+
+ @Nullable
+ private static PrivateKey getRsaPrivateKeyFromIkeSession(@NonNull IkeSessionParams params) {
+ if (!(params.getLocalAuthConfig() instanceof IkeAuthDigitalSignLocalConfig)) return null;
+
+ final IkeAuthDigitalSignLocalConfig config =
+ (IkeAuthDigitalSignLocalConfig) params.getLocalAuthConfig();
+ return config.getPrivateKey();
+ }
+
+ @Nullable
+ private static byte[] getPresharedKeyFromIkeSession(@NonNull IkeSessionParams params) {
+ if (!(params.getLocalAuthConfig() instanceof IkeAuthPskConfig)) return null;
+
+ final IkeAuthPskConfig config = (IkeAuthPskConfig) params.getLocalAuthConfig();
+ return config.getPsk();
+ }
+
+ @NonNull
+ private static String getUserIdentityFromIkeSession(@NonNull IkeSessionParams params) {
+ final IkeIdentification ident = params.getLocalIdentification();
+ // Refer to VpnIkev2Utils.parseIkeIdentification().
+ if (ident instanceof IkeKeyIdIdentification) {
+ return "@#" + new String(((IkeKeyIdIdentification) ident).keyId);
+ } else if (ident instanceof IkeRfc822AddrIdentification) {
+ return "@@" + ((IkeRfc822AddrIdentification) ident).rfc822Name;
+ } else if (ident instanceof IkeFqdnIdentification) {
+ return "@" + ((IkeFqdnIdentification) ident).fqdn;
+ } else if (ident instanceof IkeIpv4AddrIdentification) {
+ return ((IkeIpv4AddrIdentification) ident).ipv4Address.getHostAddress();
+ } else if (ident instanceof IkeIpv6AddrIdentification) {
+ return ((IkeIpv6AddrIdentification) ident).ipv6Address.getHostAddress();
+ } else {
+ throw new IllegalArgumentException("Unknown IkeIdentification to get user identity");
+ }
+ }
+
/** A incremental builder for IKEv2 VPN profiles */
public static final class Builder {
private int mType = -1;
@@ -671,6 +781,7 @@
private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;
private boolean mIsRestrictedToTestNetworks = false;
private boolean mExcludeLocalRoutes = false;
+ @Nullable private IkeTunnelConnectionParams mIkeTunConnParams;
/**
* Creates a new builder with the basic parameters of an IKEv2/IPsec VPN.
@@ -687,6 +798,32 @@
mUserIdentity = identity;
}
+ /**
+ * Creates a new builder from a {@link IkeTunnelConnectionParams}
+ *
+ * @param ikeTunConnParams the {@link IkeTunnelConnectionParams} contains IKEv2
+ * configurations
+ */
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ public Builder(@NonNull IkeTunnelConnectionParams ikeTunConnParams) {
+ checkNotNull(ikeTunConnParams, MISSING_PARAM_MSG_TMPL, "ikeTunConnParams");
+
+ mIkeTunConnParams = ikeTunConnParams;
+
+ final IkeSessionParams ikeSessionParams = mIkeTunConnParams.getIkeSessionParams();
+ mServerAddr = ikeSessionParams.getServerHostname();
+
+ mType = getTypeFromIkeSession(ikeSessionParams);
+ mUserCert = getUserCertFromIkeSession(ikeSessionParams);
+ mServerRootCaCert = getServerRootCaCertFromIkeSession(ikeSessionParams);
+ mRsaPrivateKey = getRsaPrivateKeyFromIkeSession(ikeSessionParams);
+ mServerRootCaCert = getServerRootCaCertFromIkeSession(ikeSessionParams);
+ mUsername = getUsernameFromIkeSession(ikeSessionParams);
+ mPassword = getPasswordFromIkeSession(ikeSessionParams);
+ mPresharedKey = getPresharedKeyFromIkeSession(ikeSessionParams);
+ mUserIdentity = getUserIdentityFromIkeSession(ikeSessionParams);
+ }
+
private void resetAuthParams() {
mPresharedKey = null;
mServerRootCaCert = null;
@@ -719,6 +856,7 @@
@Nullable X509Certificate serverRootCa) {
checkNotNull(user, MISSING_PARAM_MSG_TMPL, "user");
checkNotNull(pass, MISSING_PARAM_MSG_TMPL, "pass");
+ checkBuilderSetter(mIkeTunConnParams != null, "authUsernamePassword");
// Test to make sure all auth params can be encoded safely.
if (serverRootCa != null) checkCert(serverRootCa);
@@ -755,6 +893,7 @@
@Nullable X509Certificate serverRootCa) {
checkNotNull(userCert, MISSING_PARAM_MSG_TMPL, "userCert");
checkNotNull(key, MISSING_PARAM_MSG_TMPL, "key");
+ checkBuilderSetter(mIkeTunConnParams != null, "authDigitalSignature");
// Test to make sure all auth params can be encoded safely.
checkCert(userCert);
@@ -782,6 +921,7 @@
@RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
public Builder setAuthPsk(@NonNull byte[] psk) {
checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk");
+ checkBuilderSetter(mIkeTunConnParams != null, "authPsk");
resetAuthParams();
mPresharedKey = psk;
@@ -931,8 +1071,6 @@
*
* Note that because the local traffic will always bypass the VPN,
* it is not possible to set this flag on a non-bypassable VPN.
- *
- * @hide TODO(184750836): unhide once the implementation is completed
*/
@NonNull
@RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java
index 777a90c..3c45799 100644
--- a/core/java/android/net/PlatformVpnProfile.java
+++ b/core/java/android/net/PlatformVpnProfile.java
@@ -83,8 +83,6 @@
/**
* Returns if the local traffic is exempted from the VPN.
- *
- * @hide TODO(184750836): unhide once the implementation is completed
*/
public final boolean getExcludeLocalRoutes() {
return mExcludeLocalRoutes;
diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
new file mode 100644
index 0000000..24c22a9
--- /dev/null
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.net.netstats;
+
+import static android.app.usage.NetworkStatsManager.PREFIX_UID;
+import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
+import static android.app.usage.NetworkStatsManager.PREFIX_XT;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+
+import android.annotation.NonNull;
+import android.net.NetworkIdentity;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.Environment;
+import android.util.AtomicFile;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastDataInput;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class to read old version of persistent network statistics, the implementation is
+ * intended to be modified by OEM partners to accommodate their custom changes.
+ * @hide
+ */
+// @SystemApi(client = MODULE_LIBRARIES)
+public class NetworkStatsDataMigrationUtils {
+
+ private static final HashMap<String, String> sPrefixLegacyFileNameMap =
+ new HashMap<String, String>() {{
+ put(PREFIX_XT, "netstats_xt.bin");
+ put(PREFIX_UID, "netstats_uid.bin");
+ put(PREFIX_UID_TAG, "netstats_uid.bin");
+ }};
+
+ // These version constants are copied from NetworkStatsCollection/History, which is okay for
+ // OEMs to modify to adapt their own logic.
+ private static class CollectionVersion {
+ static final int VERSION_NETWORK_INIT = 1;
+
+ static final int VERSION_UID_INIT = 1;
+ static final int VERSION_UID_WITH_IDENT = 2;
+ static final int VERSION_UID_WITH_TAG = 3;
+ static final int VERSION_UID_WITH_SET = 4;
+
+ static final int VERSION_UNIFIED_INIT = 16;
+ }
+
+ private static class HistoryVersion {
+ static final int VERSION_INIT = 1;
+ static final int VERSION_ADD_PACKETS = 2;
+ static final int VERSION_ADD_ACTIVE = 3;
+ }
+
+ private static class IdentitySetVersion {
+ static final int VERSION_INIT = 1;
+ static final int VERSION_ADD_ROAMING = 2;
+ static final int VERSION_ADD_NETWORK_ID = 3;
+ static final int VERSION_ADD_METERED = 4;
+ static final int VERSION_ADD_DEFAULT_NETWORK = 5;
+ static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
+ }
+
+ /**
+ * File header magic number: "ANET". The definition is copied from NetworkStatsCollection,
+ * but it is fine for OEM to re-define to their own value to adapt the legacy file reading
+ * logic.
+ */
+ private static final int FILE_MAGIC = 0x414E4554;
+ /** Default buffer size from BufferedInputStream */
+ private static final int BUFFER_SIZE = 8192;
+
+ // Constructing this object is not allowed.
+ private NetworkStatsDataMigrationUtils() {
+ }
+
+ // Used to read files at /data/system/netstats_*.bin.
+ @NonNull
+ private static File getPlatformSystemDir() {
+ return new File(Environment.getDataDirectory(), "system");
+ }
+
+ // Used to read files at /data/system/netstats/<tag>.<start>-<end>.
+ @NonNull
+ private static File getPlatformBaseDir() {
+ File baseDir = new File(getPlatformSystemDir(), "netstats");
+ baseDir.mkdirs();
+ return baseDir;
+ }
+
+ // Get /data/system/netstats_*.bin legacy files. Does not check for existence.
+ @NonNull
+ private static File getLegacyBinFileForPrefix(@NonNull String prefix) {
+ return new File(getPlatformSystemDir(), sPrefixLegacyFileNameMap.get(prefix));
+ }
+
+ // List /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files.
+ @NonNull
+ private static ArrayList<File> getPlatformFileListForPrefix(@NonNull String prefix) {
+ final ArrayList<File> list = new ArrayList<>();
+ final File platformFiles = new File(getPlatformBaseDir(), "netstats");
+ if (platformFiles.exists()) {
+ for (String name : platformFiles.list()) {
+ // Skip when prefix doesn't match.
+ if (!name.startsWith(prefix + ".")) continue;
+
+ list.add(new File(platformFiles, name));
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Read legacy persisted network stats from disk. This function provides a default
+ * implementation to read persisted network stats from two different locations.
+ * And this is intended to be modified by OEM to read from custom file format or
+ * locations if necessary.
+ *
+ * @param prefix Type of data which is being read by the service.
+ * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+ * @return {@link NetworkStatsCollection} instance.
+ */
+ @NonNull
+ public static NetworkStatsCollection readPlatformCollectionLocked(
+ @NonNull String prefix, long bucketDuration) throws IOException {
+ final NetworkStatsCollection.Builder builder =
+ new NetworkStatsCollection.Builder(bucketDuration);
+
+ // Import /data/system/netstats_uid.bin legacy files if exists.
+ switch (prefix) {
+ case PREFIX_UID:
+ case PREFIX_UID_TAG:
+ final File uidFile = getLegacyBinFileForPrefix(prefix);
+ if (uidFile.exists()) {
+ readLegacyUid(builder, uidFile, PREFIX_UID_TAG.equals(prefix) ? true : false);
+ }
+ break;
+ default:
+ // Ignore other types.
+ }
+
+ // Import /data/system/netstats/[xt|uid|uid_tag].<start>-<end> legacy files if exists.
+ final ArrayList<File> platformFiles = getPlatformFileListForPrefix(prefix);
+ for (final File platformFile : platformFiles) {
+ if (platformFile.exists()) {
+ readPlatformCollection(builder, platformFile);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder,
+ @NonNull File file) throws IOException {
+ final FileInputStream is = new FileInputStream(file);
+ final FastDataInput dataIn = new FastDataInput(is, BUFFER_SIZE);
+ try {
+ readPlatformCollection(builder, dataIn);
+ } finally {
+ IoUtils.closeQuietly(dataIn);
+ }
+ }
+
+ /**
+ * Helper function to read old version of NetworkStatsCollections that resided in the platform.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder,
+ @NonNull DataInput in) throws IOException {
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case CollectionVersion.VERSION_UNIFIED_INIT: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = in.readInt();
+ final int tag = in.readInt();
+
+ final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key(
+ ident, uid, set, tag);
+ final NetworkStatsHistory history = readPlatformHistory(in);
+ builder.addEntry(key, history);
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ }
+
+ // Copied from NetworkStatsHistory#DataStreamUtils.
+ private static long[] readFullLongArray(DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ // Copied from NetworkStatsHistory#DataStreamUtils.
+ private static long[] readVarLongArray(@NonNull DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size == -1) return null;
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = readVarLong(in);
+ }
+ return values;
+ }
+
+ /**
+ * Read variable-length {@link Long} using protobuf-style approach.
+ */
+ // Copied from NetworkStatsHistory#DataStreamUtils.
+ private static long readVarLong(DataInput in) throws IOException {
+ int shift = 0;
+ long result = 0;
+ while (shift < 64) {
+ byte b = in.readByte();
+ result |= (long) (b & 0x7F) << shift;
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ shift += 7;
+ }
+ throw new ProtocolException("malformed var long");
+ }
+
+ // Copied from NetworkIdentitySet.
+ private static String readOptionalString(DataInput in) throws IOException {
+ if (in.readByte() != 0) {
+ return in.readUTF();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * This is copied from NetworkStatsHistory#NetworkStatsHistory(DataInput in). But it is fine
+ * for OEM to re-write the logic to adapt the legacy file reading.
+ */
+ @NonNull
+ private static NetworkStatsHistory readPlatformHistory(@NonNull DataInput in)
+ throws IOException {
+ final long bucketDuration;
+ final long[] bucketStart;
+ final long[] rxBytes;
+ final long[] rxPackets;
+ final long[] txBytes;
+ final long[] txPackets;
+ final long[] operations;
+ final int bucketCount;
+ long[] activeTime = new long[0];
+
+ final int version = in.readInt();
+ switch (version) {
+ case HistoryVersion.VERSION_INIT: {
+ bucketDuration = in.readLong();
+ bucketStart = readFullLongArray(in);
+ rxBytes = readFullLongArray(in);
+ rxPackets = new long[bucketStart.length];
+ txBytes = readFullLongArray(in);
+ txPackets = new long[bucketStart.length];
+ operations = new long[bucketStart.length];
+ bucketCount = bucketStart.length;
+ break;
+ }
+ case HistoryVersion.VERSION_ADD_PACKETS:
+ case HistoryVersion.VERSION_ADD_ACTIVE: {
+ bucketDuration = in.readLong();
+ bucketStart = readVarLongArray(in);
+ activeTime = (version >= HistoryVersion.VERSION_ADD_ACTIVE)
+ ? readVarLongArray(in)
+ : new long[bucketStart.length];
+ rxBytes = readVarLongArray(in);
+ rxPackets = readVarLongArray(in);
+ txBytes = readVarLongArray(in);
+ txPackets = readVarLongArray(in);
+ operations = readVarLongArray(in);
+ bucketCount = bucketStart.length;
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+
+ final NetworkStatsHistory.Builder historyBuilder =
+ new NetworkStatsHistory.Builder(bucketDuration, bucketCount);
+ for (int i = 0; i < bucketCount; i++) {
+ final NetworkStatsHistory.Entry entry = new NetworkStatsHistory.Entry(
+ bucketStart[i], activeTime[i],
+ rxBytes[i], rxPackets[i], txBytes[i], txPackets[i], operations[i]);
+ historyBuilder.addEntry(entry);
+ }
+
+ return historyBuilder.build();
+ }
+
+ @NonNull
+ private static Set<NetworkIdentity> readPlatformNetworkIdentitySet(@NonNull DataInput in)
+ throws IOException {
+ final int version = in.readInt();
+ final int size = in.readInt();
+ final Set<NetworkIdentity> set = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ if (version <= IdentitySetVersion.VERSION_INIT) {
+ final int ignored = in.readInt();
+ }
+ final int type = in.readInt();
+ final int ratType = in.readInt();
+ final String subscriberId = readOptionalString(in);
+ final String networkId;
+ if (version >= IdentitySetVersion.VERSION_ADD_NETWORK_ID) {
+ networkId = readOptionalString(in);
+ } else {
+ networkId = null;
+ }
+ final boolean roaming;
+ if (version >= IdentitySetVersion.VERSION_ADD_ROAMING) {
+ roaming = in.readBoolean();
+ } else {
+ roaming = false;
+ }
+
+ final boolean metered;
+ if (version >= IdentitySetVersion.VERSION_ADD_METERED) {
+ metered = in.readBoolean();
+ } else {
+ // If this is the old data and the type is mobile, treat it as metered. (Note that
+ // if this is a mobile network, TYPE_MOBILE is the only possible type that could be
+ // used.)
+ metered = (type == TYPE_MOBILE);
+ }
+
+ final boolean defaultNetwork;
+ if (version >= IdentitySetVersion.VERSION_ADD_DEFAULT_NETWORK) {
+ defaultNetwork = in.readBoolean();
+ } else {
+ defaultNetwork = true;
+ }
+
+ final int oemNetCapabilities;
+ if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) {
+ oemNetCapabilities = in.readInt();
+ } else {
+ oemNetCapabilities = NetworkIdentity.OEM_NONE;
+ }
+
+ // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later
+ // releases. For backward compatibility, record them as TYPE_MOBILE instead.
+ final int collapsedLegacyType = getCollapsedLegacyType(type);
+ final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
+ .setType(collapsedLegacyType)
+ .setSubscriberId(subscriberId)
+ .setWifiNetworkKey(networkId)
+ .setRoaming(roaming).setMetered(metered)
+ .setDefaultNetwork(defaultNetwork)
+ .setOemManaged(oemNetCapabilities);
+ if (type == TYPE_MOBILE && ratType != NetworkTemplate.NETWORK_TYPE_ALL) {
+ builder.setRatType(ratType);
+ }
+ set.add(builder.build());
+ }
+ return set;
+ }
+
+ private static int getCollapsedLegacyType(int networkType) {
+ // The constants are referenced from ConnectivityManager#TYPE_MOBILE_*.
+ switch (networkType) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ case 10 /* TYPE_MOBILE_FOTA */:
+ case 11 /* TYPE_MOBILE_IMS */:
+ case 12 /* TYPE_MOBILE_CBS */:
+ case 14 /* TYPE_MOBILE_IA */:
+ case 15 /* TYPE_MOBILE_EMERGENCY */:
+ return TYPE_MOBILE;
+ }
+ return networkType;
+ }
+
+ private static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder,
+ @NonNull File uidFile, boolean onlyTaggedData) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(uidFile);
+ DataInputStream in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+ try {
+ readLegacyUid(builder, in, onlyTaggedData);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Read legacy Uid statistics file format into the collection.
+ *
+ * This is copied from {@code NetworkStatsCollection#readLegacyUid}.
+ * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
+ *
+ * @param taggedData whether to read tagged data. For legacy uid files, the tagged
+ * data was stored in the same binary file with non-tagged data.
+ * But in later releases, these data should be kept in different
+ * recorders.
+ * @hide
+ */
+ @VisibleForTesting
+ public static void readLegacyUid(@NonNull NetworkStatsCollection.Builder builder,
+ @NonNull DataInput in, boolean taggedData) throws IOException {
+ try {
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case CollectionVersion.VERSION_UID_INIT: {
+ // uid := size *(UID NetworkStatsHistory)
+ // drop this data version, since we don't have a good
+ // mapping into NetworkIdentitySet.
+ break;
+ }
+ case CollectionVersion.VERSION_UID_WITH_IDENT: {
+ // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+ // drop this data version, since this version only existed
+ // for a short time.
+ break;
+ }
+ case CollectionVersion.VERSION_UID_WITH_TAG:
+ case CollectionVersion.VERSION_UID_WITH_SET: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final Set<NetworkIdentity> ident = readPlatformNetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = (version >= CollectionVersion.VERSION_UID_WITH_SET)
+ ? in.readInt()
+ : SET_DEFAULT;
+ final int tag = in.readInt();
+
+ final NetworkStatsCollection.Key key = new NetworkStatsCollection.Key(
+ ident, uid, set, tag);
+ final NetworkStatsHistory history = readPlatformHistory(in);
+
+ if ((tag == TAG_NONE) != taggedData) {
+ builder.addEntry(key, history);
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unknown version: " + version);
+ }
+ }
+ } catch (FileNotFoundException | ProtocolException e) {
+ // missing stats is okay, probably first boot
+ }
+ }
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 47a272c..de76c8f 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -673,6 +673,7 @@
public final int firstCustomConsumedPowerColumn;
public final int firstCustomUsageDurationColumn;
public final int columnCount;
+ public final Key[][] processStateKeys;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
boolean powerModelsIncluded, boolean includeProcessStateData) {
@@ -728,6 +729,28 @@
keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
}
+ if (includeProcessStateData) {
+ processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][];
+ ArrayList<Key> perProcStateKeys = new ArrayList<>();
+ for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ perProcStateKeys.clear();
+ for (int i = 0; i < keys.length; i++) {
+ for (int j = 0; j < keys[i].length; j++) {
+ if (keys[i][j].processState == processState) {
+ perProcStateKeys.add(keys[i][j]);
+ }
+ }
+ }
+ processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY);
+ }
+ } else {
+ processStateKeys = null;
+ }
+
firstCustomConsumedPowerColumn = columnIndex;
columnIndex += customPowerComponentCount;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index abe5f81..d41a5fe 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -109,6 +109,7 @@
static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper";
+ static final String XML_ATTR_DISCHARGE_DURATION = "discharge_duration";
static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining";
static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining";
static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package";
@@ -127,6 +128,7 @@
private final long mStatsDurationMs;
private final double mDischargedPowerLowerBound;
private final double mDischargedPowerUpperBound;
+ private final long mDischargeDurationMs;
private final long mBatteryTimeRemainingMs;
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
@@ -146,6 +148,7 @@
mDischargePercentage = builder.mDischargePercentage;
mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
+ mDischargeDurationMs = builder.mDischargeDurationMs;
mBatteryStatsHistory = builder.mBatteryStatsHistory;
mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
@@ -246,6 +249,13 @@
}
/**
+ * Returns the total amount of time the battery was discharging.
+ */
+ public long getDischargeDurationMs() {
+ return mDischargeDurationMs;
+ }
+
+ /**
* Returns an approximation for how much run time (in milliseconds) is remaining on
* the battery. Returns -1 if no time can be computed: either there is not
* enough current data to make a decision, or the battery is currently
@@ -321,6 +331,7 @@
mDischargePercentage = source.readInt();
mDischargedPowerLowerBound = source.readDouble();
mDischargedPowerUpperBound = source.readDouble();
+ mDischargeDurationMs = source.readLong();
mBatteryTimeRemainingMs = source.readLong();
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
@@ -378,6 +389,7 @@
dest.writeInt(mDischargePercentage);
dest.writeDouble(mDischargedPowerLowerBound);
dest.writeDouble(mDischargedPowerUpperBound);
+ dest.writeLong(mDischargeDurationMs);
dest.writeLong(mBatteryTimeRemainingMs);
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
@@ -447,6 +459,8 @@
proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
getDischargePercentage());
+ proto.write(BatteryUsageStatsAtomsProto.DISCHARGE_DURATION_MILLIS,
+ getDischargeDurationMs());
deviceBatteryConsumer.writeStatsProto(proto,
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
writeUidBatteryConsumersProto(proto, maxRawSize);
@@ -638,6 +652,7 @@
serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage);
serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound);
serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound);
+ serializer.attributeLong(null, XML_ATTR_DISCHARGE_DURATION, mDischargeDurationMs);
serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs);
serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs);
@@ -693,6 +708,8 @@
builder.setDischargedPowerRange(
parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER),
parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER));
+ builder.setDischargeDurationMs(
+ parser.getAttributeLong(null, XML_ATTR_DISCHARGE_DURATION));
builder.setBatteryTimeRemainingMs(
parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING));
builder.setChargeTimeRemainingMs(
@@ -759,6 +776,7 @@
private int mDischargePercentage;
private double mDischargedPowerLowerBoundMah;
private double mDischargedPowerUpperBoundMah;
+ private long mDischargeDurationMs;
private long mBatteryTimeRemainingMs = -1;
private long mChargeTimeRemainingMs = -1;
private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
@@ -869,6 +887,15 @@
}
/**
+ * Sets the total battery discharge time, in milliseconds.
+ */
+ @NonNull
+ public Builder setDischargeDurationMs(long durationMs) {
+ mDischargeDurationMs = durationMs;
+ return this;
+ }
+
+ /**
* Sets an approximation for how much time (in milliseconds) remains until the battery
* is fully discharged.
*/
@@ -994,6 +1021,7 @@
mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound;
mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound;
mDischargePercentage += stats.mDischargePercentage;
+ mDischargeDurationMs += stats.mDischargeDurationMs;
mStatsDurationMs = getStatsDuration() + stats.getStatsDuration();
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 81e49e9..37bd51b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -75,8 +75,9 @@
@NonNull
private final int[] mUserIds;
private final long mMaxStatsAgeMs;
- private long mFromTimestamp;
- private long mToTimestamp;
+ private final long mFromTimestamp;
+ private final long mToTimestamp;
+ private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
@@ -85,6 +86,7 @@
mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
mFromTimestamp = builder.mFromTimestamp;
mToTimestamp = builder.mToTimestamp;
+ mPowerComponents = builder.mPowerComponents;
}
@BatteryUsageStatsFlags
@@ -116,6 +118,14 @@
}
/**
+ * Returns the power components that should be estimated or null if all power components
+ * are being requested.
+ */
+ public int[] getPowerComponents() {
+ return mPowerComponents;
+ }
+
+ /**
* Returns the client's tolerance for stale battery stats. The data is allowed to be up to
* this many milliseconds out-of-date.
*/
@@ -147,6 +157,7 @@
mMaxStatsAgeMs = in.readLong();
mFromTimestamp = in.readLong();
mToTimestamp = in.readLong();
+ mPowerComponents = in.createIntArray();
}
@Override
@@ -157,6 +168,7 @@
dest.writeLong(mMaxStatsAgeMs);
dest.writeLong(mFromTimestamp);
dest.writeLong(mToTimestamp);
+ dest.writeIntArray(mPowerComponents);
}
@Override
@@ -187,6 +199,7 @@
private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
private long mFromTimestamp;
private long mToTimestamp;
+ private @BatteryConsumer.PowerComponent int[] mPowerComponents;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -248,6 +261,16 @@
}
/**
+ * Requests to return only statistics for the specified power components. The default
+ * is all power components.
+ */
+ public Builder includePowerComponents(
+ @BatteryConsumer.PowerComponent int[] powerComponents) {
+ mPowerComponents = powerComponents;
+ return this;
+ }
+
+ /**
* Requests to aggregate stored snapshots between the two supplied timestamps
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index e929920..1b7c00c 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -125,7 +125,7 @@
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
if (a != null) {
for (WeakReference<BinderProxy> ref : a) {
- if (ref.get() != null) {
+ if (!ref.refersTo(null)) {
++size;
}
}
@@ -196,7 +196,7 @@
// This ensures that ArrayList size is bounded by the maximum occupancy of
// that bucket.
for (int i = 0; i < size; ++i) {
- if (valueArray.get(i).get() == null) {
+ if (valueArray.get(i).refersTo(null)) {
valueArray.set(i, newWr);
Long[] keyArray = mMainIndexKeys[myHash];
keyArray[i] = key;
@@ -204,7 +204,7 @@
// "Randomly" check one of the remaining entries in [i+1, size), so that
// needlessly long buckets are eventually pruned.
int rnd = Math.floorMod(++mRandom, size - (i + 1));
- if (valueArray.get(i + 1 + rnd).get() == null) {
+ if (valueArray.get(i + 1 + rnd).refersTo(null)) {
remove(myHash, i + 1 + rnd);
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index fe86874..d9c9a2b 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -62,6 +62,8 @@
int[] getProfileIds(int userId, boolean enabledOnly);
boolean isUserTypeEnabled(in String userType);
boolean canAddMoreUsersOfType(in String userType);
+ int getRemainingCreatableUserCount(in String userType);
+ int getRemainingCreatableProfileCount(in String userType, int userId, boolean allowedToRemoveOne);
boolean canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne);
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e8b3ae9..3bc3ec8 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3711,10 +3711,10 @@
final int m = list.size();
int i = 0;
for (; i < m && i < n; i++) {
- list.set(i, readParcelableInternal(cl, clazz));
+ list.set(i, (T) readParcelableInternal(cl, clazz));
}
for (; i < n; i++) {
- list.add(readParcelableInternal(cl, clazz));
+ list.add((T) readParcelableInternal(cl, clazz));
}
for (; i < m; i++) {
list.remove(n);
@@ -4217,8 +4217,7 @@
* trying to instantiate an element.
*/
@Nullable
- public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
- @NonNull Class<? super T> clazz) {
+ public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
Objects.requireNonNull(clazz);
return readParcelableInternal(loader, clazz);
}
@@ -4228,8 +4227,7 @@
*/
@SuppressWarnings("unchecked")
@Nullable
- private <T extends Parcelable> T readParcelableInternal(@Nullable ClassLoader loader,
- @Nullable Class<? super T> clazz) {
+ private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
if (creator == null) {
return null;
@@ -4465,8 +4463,7 @@
* deserializing the object.
*/
@Nullable
- public <T extends Serializable> T readSerializable(@Nullable ClassLoader loader,
- @NonNull Class<? super T> clazz) {
+ public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
Objects.requireNonNull(clazz);
return readSerializableInternal(
loader == null ? getClass().getClassLoader() : loader, clazz);
@@ -4476,8 +4473,8 @@
* @param clazz The type of the serializable expected or {@code null} for performing no checks
*/
@Nullable
- private <T extends Serializable> T readSerializableInternal(@Nullable final ClassLoader loader,
- @Nullable Class<? super T> clazz) {
+ private <T> T readSerializableInternal(@Nullable final ClassLoader loader,
+ @Nullable Class<T> clazz) {
String name = readString();
if (name == null) {
// For some reason we were unable to read the name of the Serializable (either there
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 590494c..48e1116 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -16,7 +16,6 @@
package android.os;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_COMPONENT_COUNT;
import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
@@ -60,19 +59,16 @@
return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
dimensions.processState).mPowerColumnIndex);
} else if (dimensions.processState != PROCESS_STATE_ANY) {
- boolean foundSome = false;
- double totalPowerMah = 0;
- for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
- BatteryConsumer.Key key = mData.getKey(componentId, dimensions.processState);
- if (key != null) {
- foundSome = true;
- totalPowerMah += mData.getDouble(key.mPowerColumnIndex);
- }
- }
- if (!foundSome) {
+ if (!mData.layout.processStateDataIncluded) {
throw new IllegalArgumentException(
"No data included in BatteryUsageStats for " + dimensions);
}
+ final BatteryConsumer.Key[] keys =
+ mData.layout.processStateKeys[dimensions.processState];
+ double totalPowerMah = 0;
+ for (int i = keys.length - 1; i >= 0; i--) {
+ totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
+ }
return totalPowerMah;
} else {
return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index b754598..881fced 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -49,19 +49,7 @@
import java.util.concurrent.atomic.AtomicLong;
/**
- * This class gives you control of the power state of the device.
- *
- * <p>
- * <b>Device battery life will be significantly affected by the use of this API.</b>
- * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible. In most cases,
- * you'll want to use
- * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
- *
- * <p>
- * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
- * permission in an {@code <uses-permission>} element of the application's manifest.
- * </p>
+ * This class lets you query and request control of aspects of the device's power state.
*/
@SystemService(Context.POWER_SERVICE)
public final class PowerManager {
@@ -1196,6 +1184,11 @@
* Although a wake lock can be created without special permissions,
* the {@link android.Manifest.permission#WAKE_LOCK} permission is
* required to actually acquire or release the wake lock that is returned.
+ *
+ * </p><p>
+ * <b>Device battery life will be significantly affected by the use of this API.</b>
+ * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
+ * possible, and be sure to release them as soon as possible.
* </p><p class="note">
* If using this to keep the screen on, you should strongly consider using
* {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bd65a41..7b8d34b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4029,6 +4029,58 @@
}
/**
+ * Returns the remaining number of users of the given type that can be created.
+ *
+ * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
+ * @return how many additional users can be created.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public int getRemainingCreatableUserCount(@NonNull String userType) {
+ Objects.requireNonNull(userType, "userType must not be null");
+ try {
+ return mService.getRemainingCreatableUserCount(userType);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the remaining number of profiles that can be added to the context user.
+ * <p>Note that is applicable to any profile type (currently not including Restricted profiles).
+ *
+ * @param userType the type of profile, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @param allowedToRemoveOne whether removing an existing profile of given type -if there is-
+ * from the context user to make up space should be taken into account
+ * for the calculation.
+ * @return how many additional profiles can be created.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ @UserHandleAware
+ public int getRemainingCreatableProfileCount(@NonNull String userType,
+ boolean allowedToRemoveOne) {
+ Objects.requireNonNull(userType, "userType must not be null");
+ try {
+ // TODO(b/142482943): Perhaps let the following code apply to restricted users too.
+ return mService.getRemainingCreatableProfileCount(userType, mUserId,
+ allowedToRemoveOne);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether it's possible to add more managed profiles.
* if allowedToRemoveOne is true and if the user already has a managed profile, then return if
* we could add a new managed profile to this user after removing the existing one.
@@ -4038,6 +4090,7 @@
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
android.Manifest.permission.QUERY_USERS
})
public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
@@ -4057,6 +4110,7 @@
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
android.Manifest.permission.QUERY_USERS
})
public boolean canAddMoreProfilesToUser(@NonNull String userType, @UserIdInt int userId) {
@@ -4904,8 +4958,8 @@
public static int getMaxSupportedUsers() {
// Don't allow multiple users on certain builds
if (android.os.Build.ID.startsWith("JVP")) return 1;
- return SystemProperties.getInt("fw.max_users",
- Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
+ return Math.max(1, SystemProperties.getInt("fw.max_users",
+ Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)));
}
/**
diff --git a/core/java/android/service/cloudsearch/CloudSearchService.java b/core/java/android/service/cloudsearch/CloudSearchService.java
new file mode 100644
index 0000000..5efa1ac
--- /dev/null
+++ b/core/java/android/service/cloudsearch/CloudSearchService.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.service.cloudsearch;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.cloudsearch.ICloudSearchManager;
+import android.app.cloudsearch.SearchRequest;
+import android.app.cloudsearch.SearchResponse;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.cloudsearch.ICloudSearchService.Stub;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * A service for returning search results from cloud services in response to an on device query.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#MANAGE_CLOUDSEARCH} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * <uses-permission android:name="android.permission.MANAGE_CLOUDSEARCH"/>
+ * <application>
+ * <service android:name=".CtsCloudSearchService"
+ * android:exported="true"
+ * android:label="CtsCloudSearchService">
+ * <intent-filter>
+ * <action android:name="android.service.cloudsearch.CloudSearchService"/>
+ * </intent-filter>
+ * </service>
+ *
+ * <uses-library android:name="android.test.runner"/>
+ *
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class CloudSearchService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>The service must also require the {@link android.permission#MANAGE_CLOUDSEARCH}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.cloudsearch.CloudSearchService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "CloudSearchService";
+ private Handler mHandler;
+ private ICloudSearchManager mService;
+
+ private final android.service.cloudsearch.ICloudSearchService mInterface = new Stub() {
+ @Override
+ public void onSearch(SearchRequest request) {
+ mHandler.sendMessage(
+ obtainMessage(CloudSearchService::onSearch,
+ CloudSearchService.this, request));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) {
+ Log.d(TAG, "onCreate CloudSearchService");
+ }
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+ IBinder b = ServiceManager.getService(Context.CLOUDSEARCH_SERVICE);
+ mService = android.app.cloudsearch.ICloudSearchManager.Stub.asInterface(b);
+ }
+
+ /**
+ * onSearch receives the input request, retrievals the search provider's own
+ * corpus and returns the search response through returnResults below.
+ *
+ *@param request the search request passed from the client.
+ *
+ */
+ public abstract void onSearch(@NonNull SearchRequest request);
+
+ /**
+ * returnResults returnes the response and its associated requestId, where
+ * requestIs is generated by request through getRequestId().
+ *
+ *@param requestId the request ID got from request.getRequestId().
+ *@param response the search response returned from the search provider.
+ *
+ */
+ public final void returnResults(@NonNull String requestId,
+ @NonNull SearchResponse response) {
+ try {
+ mService.returnResults(mInterface.asBinder(), requestId, response);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind CloudSearchService");
+ }
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Slog.w(TAG, "Tried to bind to wrong intent (should be "
+ + SERVICE_INTERFACE + ": " + intent);
+ return null;
+ }
+}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/service/cloudsearch/ICloudSearchService.aidl
similarity index 67%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/android/service/cloudsearch/ICloudSearchService.aidl
index 5206831..104bf99 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/service/cloudsearch/ICloudSearchService.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.
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.service.cloudsearch;
+
+import android.app.cloudsearch.SearchRequest;
/**
+ * Interface from the system to CloudSearch service.
+ *
* @hide
*/
-parcelable SelectionContext;
+oneway interface ICloudSearchService {
+ void onSearch(in SearchRequest request);
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 8834cee..4cc379e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -46,6 +46,7 @@
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -63,8 +64,10 @@
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.UUID;
@@ -1333,25 +1336,30 @@
final CharSequence formattedTime =
getFormattedTime(context, time, isToday(time), userHandle);
final Resources res = context.getResources();
+ final Map<String, Object> arguments = new HashMap<>();
if (minutes < 60) {
// display as minutes
num = minutes;
- int summaryResId = shortVersion ? R.plurals.zen_mode_duration_minutes_summary_short
- : R.plurals.zen_mode_duration_minutes_summary;
- summary = res.getQuantityString(summaryResId, num, num, formattedTime);
- int line1ResId = shortVersion ? R.plurals.zen_mode_duration_minutes_short
- : R.plurals.zen_mode_duration_minutes;
- line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+ int summaryResId = shortVersion ? R.string.zen_mode_duration_minutes_summary_short
+ : R.string.zen_mode_duration_minutes_summary;
+ arguments.put("count", num);
+ arguments.put("formattedTime", formattedTime);
+ summary = PluralsMessageFormatter.format(res, arguments, summaryResId);
+ int line1ResId = shortVersion ? R.string.zen_mode_duration_minutes_short
+ : R.string.zen_mode_duration_minutes;
+ line1 = PluralsMessageFormatter.format(res, arguments, line1ResId);
line2 = res.getString(R.string.zen_mode_until, formattedTime);
} else if (minutes < DAY_MINUTES) {
// display as hours
num = Math.round(minutes / 60f);
- int summaryResId = shortVersion ? R.plurals.zen_mode_duration_hours_summary_short
- : R.plurals.zen_mode_duration_hours_summary;
- summary = res.getQuantityString(summaryResId, num, num, formattedTime);
- int line1ResId = shortVersion ? R.plurals.zen_mode_duration_hours_short
- : R.plurals.zen_mode_duration_hours;
- line1 = res.getQuantityString(line1ResId, num, num, formattedTime);
+ int summaryResId = shortVersion ? R.string.zen_mode_duration_hours_summary_short
+ : R.string.zen_mode_duration_hours_summary;
+ arguments.put("count", num);
+ arguments.put("formattedTime", formattedTime);
+ summary = PluralsMessageFormatter.format(res, arguments, summaryResId);
+ int line1ResId = shortVersion ? R.string.zen_mode_duration_hours_short
+ : R.string.zen_mode_duration_hours;
+ line1 = PluralsMessageFormatter.format(res, arguments, line1ResId);
line2 = res.getString(R.string.zen_mode_until, formattedTime);
} else {
// display as day/time
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index c452e06..08a7205 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -16,12 +16,21 @@
package android.service.selectiontoolbar;
-import android.util.Log;
+import static android.view.selectiontoolbar.SelectionToolbarManager.ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR;
+import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
+
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
import android.view.selectiontoolbar.ShowInfo;
+import java.util.UUID;
+
/**
* The default implementation of {@link SelectionToolbarRenderService}.
*
+ * <p><b>NOTE:<b/> The requests are handled on the service main thread.
+ *
* @hide
*/
// TODO(b/214122495): fix class not found then move to system service folder
@@ -29,22 +38,97 @@
private static final String TAG = "DefaultSelectionToolbarRenderService";
+ // TODO(b/215497659): handle remove if the client process dies.
+ // Only show one toolbar, dismiss the old ones and remove from cache
+ private final SparseArray<Pair<Long, RemoteSelectionToolbar>> mToolbarCache =
+ new SparseArray<>();
+
+ /**
+ * Only allow one package to create one toolbar.
+ */
+ private boolean canShowToolbar(int uid, ShowInfo showInfo) {
+ if (showInfo.getWidgetToken() != NO_TOOLBAR_ID) {
+ return true;
+ }
+ return mToolbarCache.indexOfKey(uid) < 0;
+ }
+
@Override
- public void onShow(ShowInfo showInfo,
+ public void onShow(int callingUid, ShowInfo showInfo,
SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) {
- // TODO: Add implementation
- Log.w(TAG, "onShow()");
+ if (!canShowToolbar(callingUid, showInfo)) {
+ Slog.e(TAG, "Do not allow multiple toolbar for the app.");
+ callbackWrapper.onError(ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR);
+ return;
+ }
+ long widgetToken = showInfo.getWidgetToken() == NO_TOOLBAR_ID
+ ? UUID.randomUUID().getMostSignificantBits()
+ : showInfo.getWidgetToken();
+
+ if (mToolbarCache.indexOfKey(callingUid) < 0) {
+ RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
+ widgetToken, showInfo.getHostInputToken(),
+ callbackWrapper, this::transferTouch);
+ mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
+ }
+ Slog.v(TAG, "onShow() for " + widgetToken);
+ Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
+ if (toolbarPair.first == widgetToken) {
+ toolbarPair.second.show(showInfo);
+ } else {
+ Slog.w(TAG, "onShow() for unknown " + widgetToken);
+ }
}
@Override
public void onHide(long widgetToken) {
- // TODO: Add implementation
- Log.w(TAG, "onHide()");
+ RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
+ if (toolbar != null) {
+ Slog.v(TAG, "onHide() for " + widgetToken);
+ toolbar.hide(widgetToken);
+ }
}
@Override
public void onDismiss(long widgetToken) {
- // TODO: Add implementation
- Log.w(TAG, "onDismiss()");
+ RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
+ if (toolbar != null) {
+ Slog.v(TAG, "onDismiss() for " + widgetToken);
+ toolbar.dismiss(widgetToken);
+ removeRemoteSelectionToolbarByTokenLocked(widgetToken);
+ }
+ }
+
+ @Override
+ public void onToolbarShowTimeout(int callingUid) {
+ Slog.w(TAG, "onToolbarShowTimeout for callingUid = " + callingUid);
+ Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
+ if (toolbarPair != null) {
+ RemoteSelectionToolbar remoteToolbar = toolbarPair.second;
+ remoteToolbar.dismiss(toolbarPair.first);
+ remoteToolbar.onToolbarShowTimeout();
+ mToolbarCache.remove(callingUid);
+ }
+ }
+
+ private RemoteSelectionToolbar getRemoteSelectionToolbarByTokenLocked(long widgetToken) {
+ for (int i = 0; i < mToolbarCache.size(); i++) {
+ Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
+ if (toolbarPair.first == widgetToken) {
+ return toolbarPair.second;
+ }
+ }
+ return null;
+ }
+
+ private void removeRemoteSelectionToolbarByTokenLocked(long widgetToken) {
+ for (int i = 0; i < mToolbarCache.size(); i++) {
+ Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
+ if (toolbarPair.first == widgetToken) {
+ mToolbarCache.remove(mToolbarCache.keyAt(i));
+ return;
+ }
+ }
}
}
+
diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
new file mode 100644
index 0000000..04491f0
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.selectiontoolbar;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+/**
+ * This class is the root view for the selection toolbar. It is responsible for
+ * detecting the click on the item and to also transfer input focus to the application.
+ *
+ * @hide
+ */
+@SuppressLint("ViewConstructor")
+public class FloatingToolbarRoot extends LinearLayout {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FloatingToolbarRoot";
+
+ private final IBinder mTargetInputToken;
+ private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
+ private Rect mContentRect;
+
+ public FloatingToolbarRoot(Context context, IBinder targetInputToken,
+ SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
+ super(context);
+ mTargetInputToken = targetInputToken;
+ mTransferTouchListener = transferTouchListener;
+ setFocusable(false);
+ }
+
+ /**
+ * Sets the Rect that shows the selection toolbar content.
+ */
+ public void setContentRect(Rect contentRect) {
+ mContentRect = contentRect;
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ int downX = (int) event.getX();
+ int downY = (int) event.getY();
+ if (DEBUG) {
+ Log.d(TAG, "downX=" + downX + " downY=" + downY);
+ }
+ // TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+ if (!mContentRect.contains(downX, downY)) {
+ if (DEBUG) {
+ Log.d(TAG, "Transfer touch focus to application.");
+ }
+ mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(),
+ mTargetInputToken);
+ }
+ }
+ return super.dispatchTouchEvent(event);
+ }
+}
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
index 2bd99ac..79281b8 100644
--- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
+++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
@@ -25,7 +25,8 @@
* @hide
*/
oneway interface ISelectionToolbarRenderService {
- void onShow(in ShowInfo showInfo, in ISelectionToolbarCallback callback);
+ void onConnected(in IBinder callback);
+ void onShow(int callingUid, in ShowInfo showInfo, in ISelectionToolbarCallback callback);
void onHide(long widgetToken);
- void onDismiss(long widgetToken);
+ void onDismiss(int callingUid, long widgetToken);
}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
similarity index 64%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
index 5206831..f6c47dd 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.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.
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package android.service.selectiontoolbar;
+
+import android.os.IBinder;
/**
+ * The interface from the SelectionToolbarRenderService to the system.
+ *
* @hide
*/
-parcelable SelectionContext;
+oneway interface ISelectionToolbarRenderServiceCallback {
+ void transferTouch(in IBinder source, in IBinder target);
+}
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index 95ecc4e..179d39d 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -21,72 +21,63 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.IBinder;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Size;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.SurfaceControlViewHost;
import android.view.View;
-import android.view.View.MeasureSpec;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
+import android.view.selectiontoolbar.ShowInfo;
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
-import android.widget.PopupWindow;
import android.widget.TextView;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.internal.widget.floatingtoolbar.FloatingToolbarPopup;
+import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
/**
- * A popup window used by the floating toolbar to render menu items in the local app process.
+ * This class is responsible for rendering/animation of the selection toolbar in the remote
+ * system process. It holds 2 panels (i.e. main panel and overflow panel) and an overflow
+ * button to transition between panels.
*
- * This class is responsible for the rendering/animation of the floating toolbar.
- * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
- * to transition between panels.
+ * @hide
*/
-
-final class RemoteSelectionToolbar implements FloatingToolbarPopup {
+// TODO(b/215497659): share code with LocalFloatingToolbarPopup
+final class RemoteSelectionToolbar {
+ private static final String TAG = "RemoteSelectionToolbar";
/* Minimum and maximum number of items allowed in the overflow. */
private static final int MIN_OVERFLOW_SIZE = 2;
private static final int MAX_OVERFLOW_SIZE = 4;
private final Context mContext;
- private final View mParent; // Parent for the popup window.
- private final PopupWindow mPopupWindow;
/* Margins between the popup window and its content. */
private final int mMarginHorizontal;
@@ -121,23 +112,22 @@
private final Animation.AnimationListener mOverflowAnimationListener;
private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in.
- private final Point mCoordsOnWindow = new Point(); // popup window coordinates.
- /* Temporary data holders. Reset values before using. */
- private final int[] mTmpCoords = new int[2];
-
- private final Region mTouchableRegion = new Region();
- private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
- info -> {
- info.contentInsets.setEmpty();
- info.visibleInsets.setEmpty();
- info.touchableRegion.set(mTouchableRegion);
- info.setTouchableInsets(
- ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- };
private final int mLineHeight;
private final int mIconTextSpacing;
+ private final long mSelectionToolbarToken;
+ private IBinder mHostInputToken;
+ private final SelectionToolbarRenderService.RemoteCallbackWrapper mCallbackWrapper;
+ private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
+ private int mPopupWidth;
+ private int mPopupHeight;
+ // Coordinates to show the toolbar relative to the specified view port
+ private final Point mRelativeCoordsForToolbar = new Point();
+ private List<ToolbarMenuItem> mMenuItems;
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
/**
* @see OverflowPanelViewHelper#preparePopupContent().
*/
@@ -145,7 +135,6 @@
@Override
public void run() {
setPanelsStatesAtRestingPosition();
- setContentAreaAsTouchableSurface();
mContentContainer.setAlpha(1);
}
};
@@ -159,26 +148,7 @@
private Size mMainPanelSize;
/* Menu items and click listeners */
- private final Map<MenuItemRepr, MenuItem> mMenuItems = new LinkedHashMap<>();
- private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOnMenuItemClickListener == null) {
- return;
- }
- final Object tag = v.getTag();
- if (!(tag instanceof MenuItemRepr)) {
- return;
- }
- final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag);
- if (menuItem == null) {
- return;
- }
- mOnMenuItemClickListener.onMenuItemClick(menuItem);
- }
- };
+ private final View.OnClickListener mMenuItemButtonOnClickListener;
private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards.
private boolean mIsOverflowOpen;
@@ -186,27 +156,28 @@
private int mTransitionDurationScale; // Used to scale the toolbar transition duration.
private final Rect mPreviousContentRect = new Rect();
- private int mSuggestedWidth;
- private boolean mWidthChanged = true;
- /**
- * Initializes a new floating toolbar popup.
- *
- * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
- * from.
- */
- RemoteSelectionToolbar(Context context, View parent) {
- mParent = Objects.requireNonNull(parent);
+ private final Rect mTempContentRect = new Rect();
+ private final Rect mTempContentRectForRoot = new Rect();
+ private final int[] mTempCoords = new int[2];
+
+ RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken,
+ SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
+ SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
mContext = applyDefaultTheme(context);
+ mSelectionToolbarToken = selectionToolbarToken;
+ mCallbackWrapper = callbackWrapper;
+ mTransferTouchListener = transferTouchListener;
+ mHostInputToken = hostInputToken;
+
mContentContainer = createContentContainer(mContext);
- mPopupWindow = createPopupWindow(mContentContainer);
- mMarginHorizontal = parent.getResources()
+ mMarginHorizontal = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
- mMarginVertical = parent.getResources()
+ mMarginVertical = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
- mLineHeight = context.getResources()
+ mLineHeight = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_height);
- mIconTextSpacing = context.getResources()
+ mIconTextSpacing = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
// Interpolators
@@ -245,53 +216,81 @@
mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
mCloseOverflowAnimation = new AnimationSet(true);
mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
- mShowAnimation = createEnterAnimation(mContentContainer);
+ mShowAnimation = createEnterAnimation(mContentContainer,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateFloatingToolbarRootContentRect();
+ }
+ });
mDismissAnimation = createExitAnimation(
mContentContainer,
150, // startDelay
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mPopupWindow.dismiss();
+ // TODO(b/215497659): should dismiss window after animation
mContentContainer.removeAllViews();
+ mSurfaceControlViewHost.release();
+ mSurfaceControlViewHost = null;
+ mSurfacePackage = null;
}
});
mHideAnimation = createExitAnimation(
mContentContainer,
0, // startDelay
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPopupWindow.dismiss();
- }
- });
+ null); // TODO(b/215497659): should handle hide after animation
+ mMenuItemButtonOnClickListener = v -> {
+ Object tag = v.getTag();
+ if (!(tag instanceof ToolbarMenuItem)) {
+ return;
+ }
+ mCallbackWrapper.onMenuItemClicked((ToolbarMenuItem) tag);
+ };
}
- @Override
- public boolean setOutsideTouchable(
- boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
- boolean ret = false;
- if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
- mPopupWindow.setOutsideTouchable(outsideTouchable);
- mPopupWindow.setFocusable(!outsideTouchable);
- mPopupWindow.update();
- ret = true;
+ private void updateFloatingToolbarRootContentRect() {
+ if (mSurfaceControlViewHost == null) {
+ return;
}
- mPopupWindow.setOnDismissListener(onDismiss);
- return ret;
+ final FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
+ mContentContainer.getLocationOnScreen(mTempCoords);
+ int contentLeft = mTempCoords[0];
+ int contentTop = mTempCoords[1];
+ mTempContentRectForRoot.set(contentLeft, contentTop,
+ contentLeft + mContentContainer.getWidth(),
+ contentTop + mContentContainer.getHeight());
+ root.setContentRect(mTempContentRectForRoot);
}
- /**
- * Lays out buttons for the specified menu items.
- * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items.
- */
+ private WidgetInfo createWidgetInfo() {
+ mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y,
+ mRelativeCoordsForToolbar.x + mPopupWidth,
+ mRelativeCoordsForToolbar.y + mPopupHeight);
+ return new WidgetInfo(mSelectionToolbarToken, mTempContentRect, getSurfacePackage());
+ }
+
+ private SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
+ if (mSurfaceControlViewHost == null) {
+ final FloatingToolbarRoot contentHolder = new FloatingToolbarRoot(mContext,
+ mHostInputToken, mTransferTouchListener);
+ contentHolder.addView(mContentContainer);
+ mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ mHostInputToken);
+ mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight);
+ }
+ if (mSurfacePackage == null) {
+ mSurfacePackage = mSurfaceControlViewHost.getSurfacePackage();
+ }
+ return mSurfacePackage;
+ }
+
private void layoutMenuItems(
- List<MenuItem> menuItems,
- MenuItem.OnMenuItemClickListener menuItemClickListener,
+ List<ToolbarMenuItem> menuItems,
int suggestedWidth) {
cancelOverflowAnimations();
clearPanels();
- updateMenuItems(menuItems, menuItemClickListener);
+
menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
if (!menuItems.isEmpty()) {
// Add remaining items to the overflow.
@@ -300,148 +299,98 @@
updatePopupSize();
}
- /**
- * Updates the popup's menu items without rebuilding the widget.
- * Use in place of layoutMenuItems() when the popup's views need not be reconstructed.
- *
- * @see #isLayoutRequired(List<MenuItem>)
- */
- private void updateMenuItems(
- List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
- mMenuItems.clear();
- for (MenuItem menuItem : menuItems) {
- mMenuItems.put(MenuItemRepr.of(menuItem), menuItem);
- }
- mOnMenuItemClickListener = menuItemClickListener;
+ public void onToolbarShowTimeout() {
+ mCallbackWrapper.onToolbarShowTimeout();
}
/**
- * Returns true if this popup needs a relayout to properly render the specified menu items.
+ * Show the specified selection toolbar.
*/
- private boolean isLayoutRequired(List<MenuItem> menuItems) {
- return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
- }
+ public void show(ShowInfo showInfo) {
+ debugLog("show() for " + showInfo);
- @Override
- public void setWidthChanged(boolean widthChanged) {
- mWidthChanged = widthChanged;
- }
+ mMenuItems = showInfo.getMenuItems();
+ mViewPortOnScreen.set(showInfo.getViewPortOnScreen());
- @Override
- public void setSuggestedWidth(int suggestedWidth) {
- // Check if there's been a substantial width spec change.
- int difference = Math.abs(suggestedWidth - mSuggestedWidth);
- mWidthChanged = difference > (mSuggestedWidth * 0.2);
- mSuggestedWidth = suggestedWidth;
- }
-
- @Override
- public void show(List<MenuItem> menuItems,
- MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
- if (isLayoutRequired(menuItems) || mWidthChanged) {
- dismiss();
- layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth);
- } else {
- updateMenuItems(menuItems, menuItemClickListener);
+ debugLog("show(): layoutRequired=" + showInfo.isLayoutRequired());
+ if (showInfo.isLayoutRequired()) {
+ layoutMenuItems(mMenuItems, showInfo.getSuggestedWidth());
}
+ Rect contentRect = showInfo.getContentRect();
if (!isShowing()) {
show(contentRect);
} else if (!mPreviousContentRect.equals(contentRect)) {
updateCoordinates(contentRect);
}
- mWidthChanged = false;
mPreviousContentRect.set(contentRect);
}
private void show(Rect contentRectOnScreen) {
Objects.requireNonNull(contentRectOnScreen);
- if (isShowing()) {
- return;
- }
-
mHidden = false;
mDismissed = false;
cancelDismissAndHideAnimations();
cancelOverflowAnimations();
-
refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
preparePopupContent();
- // We need to specify the position in window coordinates.
- // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
- // specify the popup position in screen coordinates.
- mPopupWindow.showAtLocation(
- mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
- setTouchableSurfaceInsetsComputer();
- runShowAnimation();
+ mCallbackWrapper.onShown(createWidgetInfo());
+ // TODO(b/215681595): Use Choreographer to coordinate for show between different thread
+ mShowAnimation.start();
}
- @Override
- public void dismiss() {
+ /**
+ * Dismiss the specified selection toolbar.
+ */
+ public void dismiss(long floatingToolbarToken) {
+ debugLog("dismiss for " + floatingToolbarToken);
if (mDismissed) {
return;
}
-
mHidden = false;
mDismissed = true;
- mHideAnimation.cancel();
- runDismissAnimation();
- setZeroTouchableSurface();
+ mHideAnimation.cancel();
+ mDismissAnimation.start();
}
- @Override
- public void hide() {
+ /**
+ * Hide the specified selection toolbar.
+ */
+ public void hide(long floatingToolbarToken) {
+ debugLog("hide for " + floatingToolbarToken);
if (!isShowing()) {
return;
}
mHidden = true;
- runHideAnimation();
- setZeroTouchableSurface();
+ mHideAnimation.start();
}
- @Override
public boolean isShowing() {
return !mDismissed && !mHidden;
}
- @Override
- public boolean isHidden() {
- return mHidden;
- }
-
- /**
- * Updates the coordinates of this popup.
- * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
- * This is a no-op if this popup is not showing.
- */
private void updateCoordinates(Rect contentRectOnScreen) {
Objects.requireNonNull(contentRectOnScreen);
- if (!isShowing() || !mPopupWindow.isShowing()) {
+ if (!isShowing()) {
return;
}
-
cancelOverflowAnimations();
refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
preparePopupContent();
- // We need to specify the position in window coordinates.
- // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
- // specify the popup position in screen coordinates.
- mPopupWindow.update(
- mCoordsOnWindow.x, mCoordsOnWindow.y,
- mPopupWindow.getWidth(), mPopupWindow.getHeight());
+ WidgetInfo widgetInfo = createWidgetInfo();
+ mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
+ mCallbackWrapper.onWidgetUpdated(widgetInfo);
}
private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
- refreshViewPort();
-
// Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
// landscape.
final int x = Math.min(
- contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
- mViewPortOnScreen.right - mPopupWindow.getWidth());
+ contentRectOnScreen.centerX() - mPopupWidth / 2,
+ mViewPortOnScreen.right - mPopupWidth);
final int y;
@@ -484,7 +433,7 @@
// There is enough space at the top of the content rect for the overflow.
// Position above and open upwards.
updateOverflowHeight(availableHeightAboveContent - margin);
- y = contentRectOnScreen.top - mPopupWindow.getHeight();
+ y = contentRectOnScreen.top - mPopupHeight;
mOpenOverflowUpwards = true;
} else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
&& availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
@@ -507,7 +456,7 @@
// Position below but open upwards.
updateOverflowHeight(availableHeightThroughContentUp - margin);
y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
- - mPopupWindow.getHeight();
+ - mPopupHeight;
mOpenOverflowUpwards = true;
} else {
// Not enough space.
@@ -517,45 +466,7 @@
mOpenOverflowUpwards = false;
}
}
-
- // We later specify the location of PopupWindow relative to the attached window.
- // The idea here is that 1) we can get the location of a View in both window coordinates
- // and screen coordinates, where the offset between them should be equal to the window
- // origin, and 2) we can use an arbitrary for this calculation while calculating the
- // location of the rootview is supposed to be least expensive.
- // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
- // the following calculation.
- mParent.getRootView().getLocationOnScreen(mTmpCoords);
- int rootViewLeftOnScreen = mTmpCoords[0];
- int rootViewTopOnScreen = mTmpCoords[1];
- mParent.getRootView().getLocationInWindow(mTmpCoords);
- int rootViewLeftOnWindow = mTmpCoords[0];
- int rootViewTopOnWindow = mTmpCoords[1];
- int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
- int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
- mCoordsOnWindow.set(
- Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
- }
-
- /**
- * Performs the "show" animation on the floating popup.
- */
- private void runShowAnimation() {
- mShowAnimation.start();
- }
-
- /**
- * Performs the "dismiss" animation on the floating popup.
- */
- private void runDismissAnimation() {
- mDismissAnimation.start();
- }
-
- /**
- * Performs the "hide" animation on the floating popup.
- */
- private void runHideAnimation() {
- mHideAnimation.start();
+ mRelativeCoordsForToolbar.set(x, y);
}
private void cancelDismissAndHideAnimations() {
@@ -625,15 +536,15 @@
isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
mOverflowButton.setX(actualOverflowButtonX);
+ updateFloatingToolbarRootContentRect();
}
};
widthAnimation.setInterpolator(mLogAccelerateInterpolator);
- widthAnimation.setDuration(getAdjustedDuration(250));
+ widthAnimation.setDuration(getAnimationDuration());
heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
- heightAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setDuration(getAnimationDuration());
overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
- overflowButtonAnimation.setDuration(getAdjustedDuration(250));
- mOpenOverflowAnimation.getAnimations().clear();
+ overflowButtonAnimation.setDuration(getAnimationDuration());
mOpenOverflowAnimation.getAnimations().clear();
mOpenOverflowAnimation.addAnimation(widthAnimation);
mOpenOverflowAnimation.addAnimation(heightAnimation);
@@ -701,14 +612,15 @@
isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
mOverflowButton.setX(actualOverflowButtonX);
+ updateFloatingToolbarRootContentRect();
}
};
widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
- widthAnimation.setDuration(getAdjustedDuration(250));
+ widthAnimation.setDuration(getAnimationDuration());
heightAnimation.setInterpolator(mLogAccelerateInterpolator);
- heightAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setDuration(getAnimationDuration());
overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
- overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setDuration(getAnimationDuration());
mCloseOverflowAnimation.getAnimations().clear();
mCloseOverflowAnimation.addAnimation(widthAnimation);
mCloseOverflowAnimation.addAnimation(heightAnimation);
@@ -756,7 +668,7 @@
mOverflowPanel.setX(0); // align left
} else {
mContentContainer.setX(// align right
- mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+ mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
mMainPanel.setX(-mContentContainer.getX()); // align right
mOverflowButton.setX(0); // align left
mOverflowPanel.setX(0); // align left
@@ -798,7 +710,7 @@
mOverflowPanel.setX(0); // align left
} else {
mContentContainer.setX(// align right
- mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+ mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
mMainPanel.setX(0); // align left
mOverflowButton.setX(// align right
containerSize.getWidth() - mOverflowButtonSize.getWidth());
@@ -866,70 +778,23 @@
width = Math.max(width, mOverflowPanelSize.getWidth());
height = Math.max(height, mOverflowPanelSize.getHeight());
}
- mPopupWindow.setWidth(width + mMarginHorizontal * 2);
- mPopupWindow.setHeight(height + mMarginVertical * 2);
- maybeComputeTransitionDurationScale();
- }
- private void refreshViewPort() {
- mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
+ mPopupWidth = width + mMarginHorizontal * 2;
+ mPopupHeight = height + mMarginVertical * 2;
+ maybeComputeTransitionDurationScale();
}
private int getAdjustedToolbarWidth(int suggestedWidth) {
int width = suggestedWidth;
- refreshViewPort();
- int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
+ int maximumWidth = mViewPortOnScreen.width() - 2 * mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
if (width <= 0) {
- width = mParent.getResources()
+ width = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
}
return Math.min(width, maximumWidth);
}
- /**
- * Sets the touchable region of this popup to be zero. This means that all touch events on
- * this popup will go through to the surface behind it.
- */
- private void setZeroTouchableSurface() {
- mTouchableRegion.setEmpty();
- }
-
- /**
- * Sets the touchable region of this popup to be the area occupied by its content.
- */
- private void setContentAreaAsTouchableSurface() {
- Objects.requireNonNull(mMainPanelSize);
- final int width;
- final int height;
- if (mIsOverflowOpen) {
- Objects.requireNonNull(mOverflowPanelSize);
- width = mOverflowPanelSize.getWidth();
- height = mOverflowPanelSize.getHeight();
- } else {
- width = mMainPanelSize.getWidth();
- height = mMainPanelSize.getHeight();
- }
- mTouchableRegion.set(
- (int) mContentContainer.getX(),
- (int) mContentContainer.getY(),
- (int) mContentContainer.getX() + width,
- (int) mContentContainer.getY() + height);
- }
-
- /**
- * Make the touchable area of this popup be the area specified by mTouchableRegion.
- * This should be called after the popup window has been dismissed (dismiss/hide)
- * and is probably being re-shown with a new content root view.
- */
- private void setTouchableSurfaceInsetsComputer() {
- ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView()
- .getRootView()
- .getViewTreeObserver();
- viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer);
- viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer);
- }
-
private boolean isInRTLMode() {
return mContext.getApplicationInfo().hasRtlSupport()
&& mContext.getResources().getConfiguration().getLayoutDirection()
@@ -946,18 +811,14 @@
*
* @return The menu items that are not included in this main panel.
*/
- public List<MenuItem> layoutMainPanelItems(
- List<MenuItem> menuItems, final int toolbarWidth) {
- Objects.requireNonNull(menuItems);
-
- int availableWidth = toolbarWidth;
-
- final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>();
+ private List<ToolbarMenuItem> layoutMainPanelItems(List<ToolbarMenuItem> menuItems,
+ int toolbarWidth) {
+ final LinkedList<ToolbarMenuItem> remainingMenuItems = new LinkedList<>();
// add the overflow menu items to the end of the remainingMenuItems list.
- final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
- for (MenuItem menuItem : menuItems) {
+ final LinkedList<ToolbarMenuItem> overflowMenuItems = new LinkedList();
+ for (ToolbarMenuItem menuItem : menuItems) {
if (menuItem.getItemId() != android.R.id.textAssist
- && menuItem.requiresOverflow()) {
+ && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
overflowMenuItems.add(menuItem);
} else {
remainingMenuItems.add(menuItem);
@@ -968,25 +829,22 @@
mMainPanel.removeAllViews();
mMainPanel.setPaddingRelative(0, 0, 0, 0);
- int lastGroupId = -1;
+ int availableWidth = toolbarWidth;
boolean isFirstItem = true;
while (!remainingMenuItems.isEmpty()) {
- final MenuItem menuItem = remainingMenuItems.peek();
-
+ ToolbarMenuItem menuItem = remainingMenuItems.peek();
// if this is the first item, regardless of requiresOverflow(), it should be
// displayed on the main panel. Otherwise all items including this one will be
// overflow items, and should be displayed in overflow panel.
- if (!isFirstItem && menuItem.requiresOverflow()) {
+ if (!isFirstItem && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
break;
}
-
final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
final View menuItemButton = createMenuItemButton(
mContext, menuItem, mIconTextSpacing, showIcon);
if (!showIcon && menuItemButton instanceof LinearLayout) {
((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
}
-
// Adding additional start padding for the first button to even out button spacing.
if (isFirstItem) {
menuItemButton.setPaddingRelative(
@@ -995,7 +853,6 @@
menuItemButton.getPaddingEnd(),
menuItemButton.getPaddingBottom());
}
-
// Adding additional end padding for the last button to even out button spacing.
boolean isLastItem = remainingMenuItems.size() == 1;
if (isLastItem) {
@@ -1005,18 +862,17 @@
(int) (1.5 * menuItemButton.getPaddingEnd()),
menuItemButton.getPaddingBottom());
}
-
- menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ menuItemButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
final int menuItemButtonWidth = Math.min(
menuItemButton.getMeasuredWidth(), toolbarWidth);
-
// Check if we can fit an item while reserving space for the overflowButton.
final boolean canFitWithOverflow =
menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
final boolean canFitNoOverflow =
isLastItem && menuItemButtonWidth <= availableWidth;
if (canFitWithOverflow || canFitNoOverflow) {
- setButtonTagAndClickListener(menuItemButton, menuItem);
+ menuItemButton.setTag(menuItem);
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
// Set tooltips for main panel items, but not overflow items (b/35726766).
menuItemButton.setTooltipText(menuItem.getTooltipText());
mMainPanel.addView(menuItemButton);
@@ -1028,22 +884,20 @@
} else {
break;
}
- lastGroupId = menuItem.getGroupId();
isFirstItem = false;
}
-
if (!remainingMenuItems.isEmpty()) {
// Reserve space for overflowButton.
mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
}
-
mMainPanelSize = measure(mMainPanel);
+
return remainingMenuItems;
}
- private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
- ArrayAdapter<MenuItem> overflowPanelAdapter =
- (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ private void layoutOverflowPanelItems(List<ToolbarMenuItem> menuItems) {
+ ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
+ (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
overflowPanelAdapter.clear();
final int size = menuItems.size();
for (int i = 0; i < size; i++) {
@@ -1055,7 +909,6 @@
} else {
mOverflowPanel.setY(mOverflowButtonSize.getHeight());
}
-
int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
mOverflowPanelSize = new Size(width, height);
@@ -1067,7 +920,6 @@
*/
private void preparePopupContent() {
mContentContainer.removeAllViews();
-
// Add views in the specified order so they stack up as expected.
// Order: overflowPanel, mainPanel, overflowButton.
if (hasOverflow()) {
@@ -1078,7 +930,6 @@
mContentContainer.addView(mOverflowButton);
}
setPanelsStatesAtRestingPosition();
- setContentAreaAsTouchableSurface();
// The positioning of contents in RTL is wrong when the view is first rendered.
// Hide the view and post a runnable to recalculate positions and render the view.
@@ -1093,12 +944,12 @@
* Clears out the panels and their container. Resets their calculated sizes.
*/
private void clearPanels() {
- mOverflowPanelSize = null;
- mMainPanelSize = null;
mIsOverflowOpen = false;
+ mMainPanelSize = null;
mMainPanel.removeAllViews();
- ArrayAdapter<MenuItem> overflowPanelAdapter =
- (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ mOverflowPanelSize = null;
+ ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
+ (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
overflowPanelAdapter.clear();
mOverflowPanel.setAdapter(overflowPanelAdapter);
mContentContainer.removeAllViews();
@@ -1116,7 +967,7 @@
int overflowWidth = 0;
final int count = mOverflowPanel.getAdapter().getCount();
for (int i = 0; i < count; i++) {
- MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+ ToolbarMenuItem menuItem = (ToolbarMenuItem) mOverflowPanel.getAdapter().getItem(i);
overflowWidth =
Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
}
@@ -1141,29 +992,24 @@
+ extension;
}
- private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
- menuItemButton.setTag(MenuItemRepr.of(menuItem));
- menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
- }
-
/**
* NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
* animations. See comment about this in the code.
*/
- private int getAdjustedDuration(int originalDuration) {
+ private int getAnimationDuration() {
if (mTransitionDurationScale < 150) {
// For smaller transition, decrease the time.
- return Math.max(originalDuration - 50, 0);
+ return 200;
} else if (mTransitionDurationScale > 300) {
// For bigger transition, increase the time.
- return originalDuration + 50;
+ return 300;
}
// Scale the animation duration with getDurationScale(). This allows
// android.view.animation.* animations to scale just like android.animation.* animations
// when animator duration scale is adjusted in "Developer Options".
// For this reason, do not use this method for android.animation.* animations.
- return (int) (originalDuration * ValueAnimator.getDurationScale());
+ return (int) (250 * ValueAnimator.getDurationScale());
}
private void maybeComputeTransitionDurationScale() {
@@ -1176,7 +1022,7 @@
}
private ViewGroup createMainPanel() {
- ViewGroup mainPanel = new LinearLayout(mContext) {
+ return new LinearLayout(mContext) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isOverflowAnimating()) {
@@ -1195,7 +1041,6 @@
return isOverflowAnimating();
}
};
- return mainPanel;
}
private ImageButton createOverflowButton() {
@@ -1203,6 +1048,12 @@
.inflate(R.layout.floating_popup_overflow_button, null);
overflowButton.setImageDrawable(mOverflow);
overflowButton.setOnClickListener(v -> {
+ if (isShowing()) {
+ preparePopupContent();
+ WidgetInfo widgetInfo = createWidgetInfo();
+ mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
+ mCallbackWrapper.onWidgetUpdated(widgetInfo);
+ }
if (mIsOverflowOpen) {
overflowButton.setImageDrawable(mToOverflow);
mToOverflow.start();
@@ -1224,7 +1075,7 @@
overflowPanel.setDividerHeight(0);
final ArrayAdapter adapter =
- new ArrayAdapter<MenuItem>(mContext, 0) {
+ new ArrayAdapter<ToolbarMenuItem>(mContext, 0) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return mOverflowPanelViewHelper.getView(
@@ -1232,14 +1083,11 @@
}
};
overflowPanel.setAdapter(adapter);
-
overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
- MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
- if (mOnMenuItemClickListener != null) {
- mOnMenuItemClickListener.onMenuItemClick(menuItem);
- }
+ ToolbarMenuItem menuItem =
+ (ToolbarMenuItem) overflowPanel.getAdapter().getItem(position);
+ mCallbackWrapper.onMenuItemClicked(menuItem);
});
-
return overflowPanel;
}
@@ -1252,7 +1100,7 @@
}
private Animation.AnimationListener createOverflowAnimationListener() {
- Animation.AnimationListener listener = new Animation.AnimationListener() {
+ return new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// Disable the overflow button while it's animating.
@@ -1270,7 +1118,6 @@
// actually ends.
mContentContainer.post(() -> {
setPanelsStatesAtRestingPosition();
- setContentAreaAsTouchableSurface();
});
}
@@ -1278,12 +1125,11 @@
public void onAnimationRepeat(Animation animation) {
}
};
- return listener;
}
private static Size measure(View view) {
Preconditions.checkState(view.getParent() == null);
- view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
}
@@ -1372,13 +1218,11 @@
* A helper for generating views for the overflow panel.
*/
private static final class OverflowPanelViewHelper {
-
+ private final Context mContext;
private final View mCalculator;
private final int mIconTextSpacing;
private final int mSidePadding;
- private final Context mContext;
-
OverflowPanelViewHelper(Context context, int iconTextSpacing) {
mContext = Objects.requireNonNull(context);
mIconTextSpacing = iconTextSpacing;
@@ -1387,7 +1231,7 @@
mCalculator = createMenuButton(null);
}
- public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
+ public View getView(ToolbarMenuItem menuItem, int minimumWidth, View convertView) {
Objects.requireNonNull(menuItem);
if (convertView != null) {
updateMenuItemButton(
@@ -1399,7 +1243,7 @@
return convertView;
}
- public int calculateWidth(MenuItem menuItem) {
+ public int calculateWidth(ToolbarMenuItem menuItem) {
updateMenuItemButton(
mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
mCalculator.measure(
@@ -1407,18 +1251,18 @@
return mCalculator.getMeasuredWidth();
}
- private View createMenuButton(MenuItem menuItem) {
+ private View createMenuButton(ToolbarMenuItem menuItem) {
View button = createMenuItemButton(
mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
button.setPadding(mSidePadding, 0, mSidePadding, 0);
return button;
}
- private boolean shouldShowIcon(MenuItem menuItem) {
+ private boolean shouldShowIcon(ToolbarMenuItem menuItem) {
if (menuItem != null) {
return menuItem.getGroupId() == android.R.id.textAssist;
}
- return false;
+ return false;
}
}
@@ -1426,7 +1270,7 @@
* Creates and returns a menu button for the specified menu item.
*/
private static View createMenuItemButton(
- Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+ Context context, ToolbarMenuItem menuItem, int iconTextSpacing, boolean showIcon) {
final View menuItemButton = LayoutInflater.from(context)
.inflate(R.layout.floating_popup_menu_button, null);
if (menuItem != null) {
@@ -1438,8 +1282,8 @@
/**
* Updates the specified menu item button with the specified menu item data.
*/
- private static void updateMenuItemButton(
- View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+ private static void updateMenuItemButton(View menuItemButton, ToolbarMenuItem menuItem,
+ int iconTextSpacing, boolean showIcon) {
final TextView buttonText = menuItemButton.findViewById(
R.id.floating_toolbar_menu_item_text);
buttonText.setEllipsize(null);
@@ -1453,15 +1297,12 @@
R.id.floating_toolbar_menu_item_image);
if (menuItem.getIcon() == null || !showIcon) {
buttonIcon.setVisibility(View.GONE);
- if (buttonText != null) {
- buttonText.setPaddingRelative(0, 0, 0, 0);
- }
+ buttonText.setPaddingRelative(0, 0, 0, 0);
} else {
buttonIcon.setVisibility(View.VISIBLE);
- buttonIcon.setImageDrawable(menuItem.getIcon());
- if (buttonText != null) {
- buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
- }
+ buttonIcon.setImageDrawable(
+ menuItem.getIcon().loadDrawable(menuItemButton.getContext()));
+ buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
}
final CharSequence contentDescription = menuItem.getContentDescription();
if (TextUtils.isEmpty(contentDescription)) {
@@ -1476,36 +1317,21 @@
.inflate(R.layout.floating_popup_container, null);
contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- contentContainer.setTag("floating_toolbar");
+ contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
contentContainer.setClipToOutline(true);
return contentContainer;
}
- private static PopupWindow createPopupWindow(ViewGroup content) {
- ViewGroup popupContentHolder = new LinearLayout(content.getContext());
- PopupWindow popupWindow = new PopupWindow(popupContentHolder);
- // TODO: Use .setIsLaidOutInScreen(true) instead of .setClippingEnabled(false)
- // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects.
- popupWindow.setClippingEnabled(false);
- popupWindow.setWindowLayoutType(
- WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
- popupWindow.setAnimationStyle(0);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- content.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- popupContentHolder.addView(content);
- return popupWindow;
- }
-
/**
* Creates an "appear" animation for the specified view.
*
* @param view The view to animate
*/
- private static AnimatorSet createEnterAnimation(View view) {
+ private static AnimatorSet createEnterAnimation(View view, Animator.AnimatorListener listener) {
AnimatorSet animation = new AnimatorSet();
animation.playTogether(
ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
+ animation.addListener(listener);
return animation;
}
@@ -1522,7 +1348,9 @@
animation.playTogether(
ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
animation.setStartDelay(startDelay);
- animation.addListener(listener);
+ if (listener != null) {
+ animation.addListener(listener);
+ }
return animation;
}
@@ -1538,82 +1366,9 @@
return new ContextThemeWrapper(originalContext, themeId);
}
- /**
- * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
- */
- @VisibleForTesting
- public static final class MenuItemRepr {
-
- public final int itemId;
- public final int groupId;
- @Nullable public final String title;
- @Nullable private final Drawable mIcon;
-
- private MenuItemRepr(
- int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) {
- this.itemId = itemId;
- this.groupId = groupId;
- this.title = (title == null) ? null : title.toString();
- mIcon = icon;
- }
-
- /**
- * Creates an instance of MenuItemRepr for the specified menu item.
- */
- public static MenuItemRepr of(MenuItem menuItem) {
- return new MenuItemRepr(
- menuItem.getItemId(),
- menuItem.getGroupId(),
- menuItem.getTitle(),
- menuItem.getIcon());
- }
-
- /**
- * Returns this object's hashcode.
- */
- @Override
- public int hashCode() {
- return Objects.hash(itemId, groupId, title, mIcon);
- }
-
- /**
- * Returns true if this object is the same as the specified object.
- */
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof MenuItemRepr)) {
- return false;
- }
- final MenuItemRepr other = (MenuItemRepr) o;
- return itemId == other.itemId
- && groupId == other.groupId
- && TextUtils.equals(title, other.title)
- // Many Drawables (icons) do not implement equals(). Using equals() here instead
- // of reference comparisons in case a Drawable subclass implements equals().
- && Objects.equals(mIcon, other.mIcon);
- }
-
- /**
- * Returns true if the two menu item collections are the same based on MenuItemRepr.
- */
- public static boolean reprEquals(
- Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
- if (menuItems1.size() != menuItems2.size()) {
- return false;
- }
-
- final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
- for (MenuItem menuItem1 : menuItems1) {
- final MenuItem menuItem2 = menuItems2Iter.next();
- if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) {
- return false;
- }
- }
-
- return true;
+ private static void debugLog(String message) {
+ if (Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.DEBUG)) {
+ Log.v(TAG, message);
}
}
}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
index 6468183..a10b6a8 100644
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
@@ -31,14 +31,6 @@
*/
void onShown(WidgetInfo widgetInfo);
/**
- * The selection toolbar is hidden.
- */
- void onHidden(long widgetToken);
- /**
- * The selection toolbar is dismissed.
- */
- void onDismissed(long widgetToken);
- /**
* The selection toolbar has changed.
*/
void onWidgetUpdated(WidgetInfo info);
@@ -47,6 +39,10 @@
*/
void onMenuItemClicked(ToolbarMenuItem item);
/**
+ * The toolbar doesn't be dismissed after showing on a given timeout.
+ */
+ void onToolbarShowTimeout();
+ /**
* The error occurred when operating on the selection toolbar.
*/
void onError(int errorCode);
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
index 6f66c9f..f33feae 100644
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
@@ -28,6 +28,8 @@
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.selectiontoolbar.ISelectionToolbarCallback;
import android.view.selectiontoolbar.ShowInfo;
import android.view.selectiontoolbar.ToolbarMenuItem;
@@ -42,6 +44,10 @@
private static final String TAG = "SelectionToolbarRenderService";
+ // TODO(b/215497659): read from DeviceConfig
+ // The timeout to clean the cache if the client forgot to call dismiss()
+ private static final int CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS = 10 * 60 * 1000; // 10 minutes
+
/**
* The {@link Intent} that must be declared as handled by the service.
*
@@ -53,6 +59,10 @@
"android.service.selectiontoolbar.SelectionToolbarRenderService";
private Handler mHandler;
+ private ISelectionToolbarRenderServiceCallback mServiceCallback;
+
+ private final SparseArray<Pair<RemoteCallbackWrapper, CleanCacheRunnable>> mCache =
+ new SparseArray<>();
/**
* Binder to receive calls from system server.
@@ -61,10 +71,18 @@
new ISelectionToolbarRenderService.Stub() {
@Override
- public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+ public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
+ if (mCache.indexOfKey(callingUid) < 0) {
+ mCache.put(callingUid, new Pair<>(new RemoteCallbackWrapper(callback),
+ new CleanCacheRunnable(callingUid)));
+ }
+ Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
+ CleanCacheRunnable cleanRunnable = toolbarPair.second;
+ mHandler.removeCallbacks(cleanRunnable);
mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow,
- SelectionToolbarRenderService.this, showInfo,
- new RemoteCallbackWrapper(callback)));
+ SelectionToolbarRenderService.this, callingUid, showInfo,
+ toolbarPair.first));
+ mHandler.postDelayed(cleanRunnable, CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS);
}
@Override
@@ -74,9 +92,20 @@
}
@Override
- public void onDismiss(long widgetToken) {
+ public void onDismiss(int callingUid, long widgetToken) {
mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss,
SelectionToolbarRenderService.this, widgetToken));
+ Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
+ if (toolbarPair != null) {
+ mHandler.removeCallbacks(toolbarPair.second);
+ mCache.remove(callingUid);
+ }
+ }
+
+ @Override
+ public void onConnected(IBinder callback) {
+ mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::handleOnConnected,
+ SelectionToolbarRenderService.this, callback));
}
};
@@ -97,11 +126,28 @@
return null;
}
+ private void handleOnConnected(@NonNull IBinder callback) {
+ mServiceCallback = ISelectionToolbarRenderServiceCallback.Stub.asInterface(callback);
+ }
+
+ protected void transferTouch(@NonNull IBinder source, @NonNull IBinder target) {
+ final ISelectionToolbarRenderServiceCallback callback = mServiceCallback;
+ if (callback == null) {
+ Log.e(TAG, "transferTouch(): no server callback");
+ return;
+ }
+ try {
+ callback.transferTouch(source, target);
+ } catch (RemoteException e) {
+ // no-op
+ }
+ }
/**
* Called when showing the selection toolbar.
*/
- public abstract void onShow(ShowInfo showInfo, RemoteCallbackWrapper callbackWrapper);
+ public abstract void onShow(int callingUid, ShowInfo showInfo,
+ RemoteCallbackWrapper callbackWrapper);
/**
* Called when hiding the selection toolbar.
@@ -115,13 +161,22 @@
public abstract void onDismiss(long widgetToken);
/**
- * Add avadoc.
+ * Called when showing the selection toolbar for a specific timeout. This avoids the client
+ * forgot to call dismiss to clean the state.
+ */
+ public void onToolbarShowTimeout(int callingUid) {
+ // no-op
+ }
+
+ /**
+ * Callback to notify the client toolbar events.
*/
public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback {
private final ISelectionToolbarCallback mRemoteCallback;
RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) {
+ // TODO(b/215497659): handle if the binder dies.
mRemoteCallback = remoteCallback;
}
@@ -130,25 +185,16 @@
try {
mRemoteCallback.onShown(widgetInfo);
} catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ // no-op
}
}
@Override
- public void onHidden(long widgetToken) {
+ public void onToolbarShowTimeout() {
try {
- mRemoteCallback.onHidden(widgetToken);
+ mRemoteCallback.onToolbarShowTimeout();
} catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
- }
-
- @Override
- public void onDismissed(long widgetToken) {
- try {
- mRemoteCallback.onDismissed(widgetToken);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ // no-op
}
}
@@ -157,7 +203,7 @@
try {
mRemoteCallback.onWidgetUpdated(widgetInfo);
} catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ // no-op
}
}
@@ -166,7 +212,7 @@
try {
mRemoteCallback.onMenuItemClicked(item);
} catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ // no-op
}
}
@@ -175,8 +221,37 @@
try {
mRemoteCallback.onError(errorCode);
} catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ // no-op
}
}
}
+
+ private class CleanCacheRunnable implements Runnable {
+
+ int mCleanUid;
+
+ CleanCacheRunnable(int cleanUid) {
+ mCleanUid = cleanUid;
+ }
+
+ @Override
+ public void run() {
+ Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(mCleanUid);
+ if (toolbarPair != null) {
+ Log.w(TAG, "CleanCacheRunnable: remove " + mCleanUid + " from cache.");
+ mCache.remove(mCleanUid);
+ onToolbarShowTimeout(mCleanUid);
+ }
+ }
+ }
+
+ /**
+ * A listener to notify the service to the transfer touch focus.
+ */
+ public interface TransferTouchListener {
+ /**
+ * Notify the service to transfer the touch focus.
+ */
+ void onTransferTouch(IBinder source, IBinder target);
+ }
}
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index dbe1089..1922607 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -44,14 +44,17 @@
private final IVoiceInteractionManagerService mManagerService;
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
+ private final int mDetectorType;
AbstractHotwordDetector(
IVoiceInteractionManagerService managerService,
- HotwordDetector.Callback callback) {
+ HotwordDetector.Callback callback,
+ int detectorType) {
mManagerService = managerService;
// TODO: this needs to be supplied from above
mHandler = new Handler(Looper.getMainLooper());
mCallback = callback;
+ mDetectorType = detectorType;
}
/**
@@ -104,19 +107,20 @@
Slog.d(TAG, "updateState()");
}
synchronized (mLock) {
- updateStateLocked(options, sharedMemory, null /* callback */);
+ updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
}
}
protected void updateStateLocked(@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
+ @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback,
+ int detectorType) {
if (DEBUG) {
Slog.d(TAG, "updateStateLocked()");
}
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
- mManagerService.updateState(identity, options, sharedMemory, callback);
+ mManagerService.updateState(identity, options, sharedMemory, callback, detectorType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index face870..c9daf52 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -578,7 +578,9 @@
IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
boolean supportHotwordDetectionService, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
- super(modelManagementService, callback);
+ super(modelManagementService, callback,
+ supportHotwordDetectionService ? DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
+ : DETECTOR_TYPE_NORMAL);
mHandler = new MyHandler();
mText = text;
@@ -590,7 +592,8 @@
mTargetSdkVersion = targetSdkVersion;
mSupportHotwordDetectionService = supportHotwordDetectionService;
if (mSupportHotwordDetectionService) {
- updateStateLocked(options, sharedMemory, mInternalCallback);
+ updateStateLocked(options, sharedMemory, mInternalCallback,
+ DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
}
try {
Identity identity = new Identity();
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index e247819..969ec22 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -37,6 +37,27 @@
public interface HotwordDetector {
/**
+ * Indicates that it is a non-trusted hotword detector.
+ *
+ * @hide
+ */
+ int DETECTOR_TYPE_NORMAL = 0;
+
+ /**
+ * Indicates that it is a DSP trusted hotword detector.
+ *
+ * @hide
+ */
+ int DETECTOR_TYPE_TRUSTED_HOTWORD_DSP = 1;
+
+ /**
+ * Indicates that it is a software trusted hotword detector.
+ *
+ * @hide
+ */
+ int DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE = 2;
+
+ /**
* Starts hotword recognition.
* <p>
* On calling this, the system streams audio from the device microphone to this application's
@@ -98,6 +119,22 @@
void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
/**
+ * @hide
+ */
+ static String detectorTypeToString(int detectorType) {
+ switch (detectorType) {
+ case DETECTOR_TYPE_NORMAL:
+ return "normal";
+ case DETECTOR_TYPE_TRUSTED_HOTWORD_DSP:
+ return "trusted_hotword_dsp";
+ case DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
+ return "trusted_hotword_software";
+ default:
+ return Integer.toString(detectorType);
+ }
+ }
+
+ /**
* The callback to notify of detection events.
*/
interface Callback {
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index f7a3415..512a654 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -60,14 +60,15 @@
PersistableBundle options,
SharedMemory sharedMemory,
HotwordDetector.Callback callback) {
- super(managerService, callback);
+ super(managerService, callback, DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
mManagerService = managerService;
mAudioFormat = audioFormat;
mCallback = callback;
mHandler = new Handler(Looper.getMainLooper());
updateStateLocked(options, sharedMemory,
- new InitializationStateListener(mHandler, mCallback));
+ new InitializationStateListener(mHandler, mCallback),
+ DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
}
@RequiresPermission(RECORD_AUDIO)
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index d0fd2b3..28f5c21 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2071,15 +2071,6 @@
}
/**
- * Return localized string representing the given number of selected items.
- *
- * @hide
- */
- public static CharSequence formatSelectedCount(int count) {
- return Resources.getSystem().getQuantityString(R.plurals.selected_count, count, count);
- }
-
- /**
* Simple alternative to {@link String#format} which purposefully supports
* only a small handful of substitutions to improve execution speed.
* Benchmarking reveals this optimized alternative performs 6.5x faster for
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 9cbda9c..2eb917b 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -306,7 +306,7 @@
/* package */ int getPrefs(Context context) {
synchronized (this) {
- if (!mPrefsInited || mResolver.get() == null) {
+ if (!mPrefsInited || mResolver.refersTo(null)) {
initPrefs(context);
}
}
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
new file mode 100644
index 0000000..18783f5
--- /dev/null
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.transparency;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.os.IBinaryTransparencyService;
+
+import java.util.Map;
+
+/**
+ * BinaryTransparencyManager defines a number of system interfaces that other system apps or
+ * services can make use of, when trying to get more information about the various binaries
+ * that are installed on this device.
+ * @hide
+ */
+@SystemService(Context.BINARY_TRANSPARENCY_SERVICE)
+public class BinaryTransparencyManager {
+ private static final String TAG = "TransparencyManager";
+
+ private final Context mContext;
+ private final IBinaryTransparencyService mService;
+
+ /**
+ * Constructor
+ * @param context The calling context.
+ * @param service A valid instance of IBinaryTransparencyService.
+ * @hide
+ */
+ public BinaryTransparencyManager(Context context, IBinaryTransparencyService service) {
+ mContext = context;
+ mService = service;
+ }
+
+
+ /**
+ * Obtains a string containing information that describes the signed images that are installed
+ * on this device. Currently, this piece of information is identified as the VBMeta digest.
+ * @return A String containing the VBMeta Digest of the signed partitions loaded on this device.
+ */
+ @NonNull
+ public String getSignedImageInfo() {
+ try {
+ return mService.getSignedImageInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a map of all installed APEXs consisting of package name to SHA256 hash of the
+ * package.
+ * @return A Map with the following entries: {apex package name : sha256 digest of package}
+ */
+ @NonNull
+ public Map getApexInfo() {
+ try {
+ Slog.d(TAG, "Calling backend's getApexInfo()");
+ return mService.getApexInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/transparency/OWNERS b/core/java/android/transparency/OWNERS
new file mode 100644
index 0000000..75bf84c
--- /dev/null
+++ b/core/java/android/transparency/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36824
+billylau@google.com
+vishwath@google.com
+mpgroover@google.com
diff --git a/core/java/android/util/PluralsMessageFormatter.java b/core/java/android/util/PluralsMessageFormatter.java
new file mode 100644
index 0000000..7cb9fb3
--- /dev/null
+++ b/core/java/android/util/PluralsMessageFormatter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.util;
+
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.content.res.Resources;
+import android.icu.text.MessageFormat;
+
+import java.util.Map;
+
+/**
+ * Helper class for easier formatting of ICU {@link android.icu.text.MessageFormat} syntax.
+ * @hide
+ */
+public class PluralsMessageFormatter {
+ /**
+ * Formatting the ICU {@link android.icu.text.MessageFormat} syntax
+ *
+ * @param resources the {@link android.content.res.Resources}
+ * @param arguments the mapping of argument names and values
+ * @param messageId the string resource id with {@link android.icu.text.MessageFormat} syntax
+ * @return the formatted result
+ */
+ public static String format(@NonNull Resources resources,
+ Map<String, Object> arguments,
+ @StringRes int messageId) {
+ return new MessageFormat(resources.getString(messageId)).format(arguments);
+ }
+}
diff --git a/core/java/android/util/SparseArrayMap.java b/core/java/android/util/SparseArrayMap.java
index cd592a7..e5bb9f45 100644
--- a/core/java/android/util/SparseArrayMap.java
+++ b/core/java/android/util/SparseArrayMap.java
@@ -62,6 +62,14 @@
}
/**
+ * Removes all the data for the keyIndex, if there was any.
+ * @hide
+ */
+ public void deleteAt(int keyIndex) {
+ mData.removeAt(keyIndex);
+ }
+
+ /**
* Removes the data for the key and mapKey, if there was any.
*
* @return Returns the value that was stored under the keys, or null if there was none.
@@ -142,6 +150,15 @@
return data == null ? 0 : data.size();
}
+ /**
+ * Returns the number of elements in the map of the given keyIndex.
+ * @hide
+ */
+ public int numElementsForKeyAt(int keyIndex) {
+ ArrayMap<K, V> data = mData.valueAt(keyIndex);
+ return data == null ? 0 : data.size();
+ }
+
/** Returns the value V at the given key and map index. */
@Nullable
public V valueAt(int keyIndex, int mapIndex) {
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
index e8d96d8..ee2e3ce 100644
--- a/core/java/android/util/SparseDoubleArray.java
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -124,6 +124,15 @@
}
/**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(int key) {
+ return mValues.indexOfKey(key);
+ }
+
+ /**
* Given an index in the range <code>0...size()-1</code>, returns
* the key from the <code>index</code>th key-value mapping that this
* SparseDoubleArray stores.
@@ -146,6 +155,34 @@
}
/**
+ * Given an index in the range <code>0...size()-1</code>, sets a new
+ * value for the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+ * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+ * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+ */
+ public void setValueAt(int index, double value) {
+ mValues.setValueAt(index, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ mValues.removeAt(index);
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(int key) {
+ mValues.delete(key);
+ }
+
+ /**
* Removes all key-value mappings from this SparseDoubleArray.
*/
public void clear() {
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 7185972..b739e37 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -245,6 +245,28 @@
}
/**
+ * Given an index in the range <code>0...size()-1</code>, sets a new
+ * value for the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
+ * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
+ * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
+ *
+ * @hide
+ */
+ public void setValueAt(int index, long value) {
+ if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
+ // The array might be slightly bigger than mSize, in which case, indexing won't fail.
+ // Check if exception should be thrown outside of the critical path.
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+
+ mValues[index] = value;
+ }
+
+ /**
* Returns the index for which {@link #keyAt} would return the
* specified key, or a negative number if the specified
* key is not mapped.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index d6e074f..fa39380 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -115,14 +115,6 @@
private int mCachedAppHeightCompat;
/**
- * Indicates that the application is started in a different rotation than the real display, so
- * the display information may be adjusted. That ensures the methods {@link #getRotation},
- * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how
- * the application window is laid out.
- */
- private boolean mMayAdjustByFixedRotation;
-
- /**
* Cache if the application is the recents component.
* TODO(b/179308296) Remove once Launcher addresses issue
*/
@@ -916,14 +908,15 @@
* degrees counter-clockwise, to compensate rendering will be rotated by
* 90 degrees clockwise and thus the returned value here will be
* {@link Surface#ROTATION_90 Surface.ROTATION_90}.
+ *
+ * This rotation value will match the results of {@link #getMetrics}: this means that the
+ * rotation value will correspond to the activity if accessed through the activity.
*/
@Surface.Rotation
public int getRotation() {
synchronized (mLock) {
updateDisplayInfoLocked();
- return mMayAdjustByFixedRotation
- ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation)
- : mDisplayInfo.rotation;
+ return getLocalRotation();
}
}
@@ -959,9 +952,15 @@
public DisplayCutout getCutout() {
synchronized (mLock) {
updateDisplayInfoLocked();
- return mMayAdjustByFixedRotation
- ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout)
- : mDisplayInfo.displayCutout;
+ if (mResources == null) return mDisplayInfo.displayCutout;
+ final DisplayCutout localCutout = mDisplayInfo.displayCutout;
+ if (localCutout == null) return null;
+ int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ return localCutout.getRotated(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+ mDisplayInfo.rotation, rotation);
+ }
+ return localCutout;
}
}
@@ -977,15 +976,11 @@
public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
synchronized (mLock) {
updateDisplayInfoLocked();
- RoundedCorners roundedCorners;
- if (mMayAdjustByFixedRotation) {
- roundedCorners = getDisplayAdjustments().adjustRoundedCorner(
- mDisplayInfo.roundedCorners,
- mDisplayInfo.rotation,
- mDisplayInfo.logicalWidth,
- mDisplayInfo.logicalHeight);
- } else {
- roundedCorners = mDisplayInfo.roundedCorners;
+ final RoundedCorners roundedCorners = mDisplayInfo.roundedCorners;
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (roundedCorners != null && rotation != mDisplayInfo.rotation) {
+ roundedCorners.rotate(rotation,
+ mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
}
return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position);
}
@@ -1468,8 +1463,9 @@
}
outSize.x = mDisplayInfo.logicalWidth;
outSize.y = mDisplayInfo.logicalHeight;
- if (mMayAdjustByFixedRotation) {
- getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation);
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ adjustSize(outSize, mDisplayInfo.rotation, rotation);
}
}
}
@@ -1537,8 +1533,9 @@
}
mDisplayInfo.getLogicalMetrics(outMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- if (mMayAdjustByFixedRotation) {
- getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation);
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ adjustMetrics(outMetrics, mDisplayInfo.rotation, rotation);
}
}
}
@@ -1663,9 +1660,6 @@
}
}
}
-
- mMayAdjustByFixedRotation = mResources != null
- && mResources.hasOverrideDisplayAdjustments();
}
private void updateCachedAppSizeIfNeededLocked() {
@@ -1679,6 +1673,49 @@
}
}
+ /** Returns {@code false} if the width and height of display should swap. */
+ private static boolean noFlip(@Surface.Rotation int realRotation,
+ @Surface.Rotation int localRotation) {
+ // Check if the delta is rotated by 90 degrees.
+ return (realRotation - localRotation + 4) % 2 == 0;
+ }
+
+ /**
+ * Adjusts the given size by a rotation offset if necessary.
+ * @hide
+ */
+ private void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation,
+ @Surface.Rotation int localRotation) {
+ if (noFlip(realRotation, localRotation)) return;
+ final int w = size.x;
+ size.x = size.y;
+ size.y = w;
+ }
+
+ /**
+ * Adjusts the given metrics by a rotation offset if necessary.
+ * @hide
+ */
+ private void adjustMetrics(@NonNull DisplayMetrics metrics,
+ @Surface.Rotation int realRotation, @Surface.Rotation int localRotation) {
+ if (noFlip(realRotation, localRotation)) return;
+ int w = metrics.widthPixels;
+ metrics.widthPixels = metrics.heightPixels;
+ metrics.heightPixels = w;
+
+ w = metrics.noncompatWidthPixels;
+ metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
+ metrics.noncompatHeightPixels = w;
+ }
+
+ private @Surface.Rotation int getLocalRotation() {
+ if (mResources == null) return mDisplayInfo.rotation;
+ final @Surface.Rotation int localRotation =
+ mResources.getConfiguration().windowConfiguration.getDisplayRotation();
+ if (localRotation != WindowConfiguration.ROTATION_UNDEFINED) return localRotation;
+ return mDisplayInfo.rotation;
+ }
+
// For debugging purposes
@Override
public String toString() {
@@ -1686,9 +1723,7 @@
updateDisplayInfoLocked();
final DisplayAdjustments adjustments = getDisplayAdjustments();
mDisplayInfo.getAppMetrics(mTempMetrics, adjustments);
- return "Display id " + mDisplayId + ": " + mDisplayInfo
- + (mMayAdjustByFixedRotation
- ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ")
+ return "Display id " + mDisplayId + ": " + mDisplayInfo + ", "
+ mTempMetrics + ", isValid=" + mIsValid;
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index e307eff..bb50849 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -21,10 +21,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.DisplayMetrics;
import java.util.Objects;
@@ -34,7 +30,6 @@
private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
private final Configuration mConfiguration = new Configuration(Configuration.EMPTY);
- private FixedRotationAdjustments mFixedRotationAdjustments;
@UnsupportedAppUsage
public DisplayAdjustments() {
@@ -49,7 +44,6 @@
public DisplayAdjustments(@NonNull DisplayAdjustments daj) {
setCompatibilityInfo(daj.mCompatInfo);
mConfiguration.setTo(daj.getConfiguration());
- mFixedRotationAdjustments = daj.mFixedRotationAdjustments;
}
@UnsupportedAppUsage
@@ -90,97 +84,11 @@
return mConfiguration;
}
- public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) {
- mFixedRotationAdjustments = fixedRotationAdjustments;
- }
-
- public FixedRotationAdjustments getFixedRotationAdjustments() {
- return mFixedRotationAdjustments;
- }
-
- /** Returns {@code false} if the width and height of display should swap. */
- private boolean noFlip(@Surface.Rotation int realRotation) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (rotationAdjustments == null) {
- return true;
- }
- // Check if the delta is rotated by 90 degrees.
- return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0;
- }
-
- /** Adjusts the given size if possible. */
- public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) {
- if (noFlip(realRotation)) {
- return;
- }
- final int w = size.x;
- size.x = size.y;
- size.y = w;
- }
-
- /** Adjusts the given metrics if possible. */
- public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) {
- if (noFlip(realRotation)) {
- return;
- }
- int w = metrics.widthPixels;
- metrics.widthPixels = metrics.heightPixels;
- metrics.heightPixels = w;
-
- w = metrics.noncompatWidthPixels;
- metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
- metrics.noncompatHeightPixels = w;
- }
-
- /** Adjusts global display metrics that is available to applications. */
- public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (rotationAdjustments == null) {
- return;
- }
- metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
- metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
- }
-
- /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
- @Nullable
- public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null
- ? rotationAdjustments.mRotatedDisplayCutout
- : realCutout;
- }
-
- /**
- * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original
- * {@link RoundedCorners} is returned.
- */
- @Nullable
- public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners,
- @Surface.Rotation int realRotation, int displayWidth, int displayHeight) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (realRoundedCorners == null || rotationAdjustments == null
- || rotationAdjustments.mRotation == realRotation) {
- return realRoundedCorners;
- }
-
- return realRoundedCorners.rotate(
- rotationAdjustments.mRotation, displayWidth, displayHeight);
- }
-
- /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
- @Surface.Rotation
- public int getRotation(@Surface.Rotation int realRotation) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation;
- }
-
@Override
public int hashCode() {
int hash = 17;
hash = hash * 31 + Objects.hashCode(mCompatInfo);
hash = hash * 31 + Objects.hashCode(mConfiguration);
- hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments);
return hash;
}
@@ -191,101 +99,6 @@
}
DisplayAdjustments daj = (DisplayAdjustments)o;
return Objects.equals(daj.mCompatInfo, mCompatInfo)
- && Objects.equals(daj.mConfiguration, mConfiguration)
- && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments);
- }
-
- /**
- * An application can be launched in different rotation than the real display. This class
- * provides the information to adjust the values returned by {@link Display}.
- * @hide
- */
- public static class FixedRotationAdjustments implements Parcelable {
- /** The application-based rotation. */
- @Surface.Rotation
- final int mRotation;
-
- /**
- * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
- * to rotation because it minus the region of screen decorations.
- */
- final int mAppWidth;
-
- /** The rotated {@link DisplayInfo#appHeight}. */
- final int mAppHeight;
-
- /** Non-null if the device has cutout. */
- @Nullable
- final DisplayCutout mRotatedDisplayCutout;
-
- public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
- DisplayCutout cutout) {
- mRotation = rotation;
- mAppWidth = appWidth;
- mAppHeight = appHeight;
- mRotatedDisplayCutout = cutout;
- }
-
- @Override
- public int hashCode() {
- int hash = 17;
- hash = hash * 31 + mRotation;
- hash = hash * 31 + mAppWidth;
- hash = hash * 31 + mAppHeight;
- hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
- return hash;
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof FixedRotationAdjustments)) {
- return false;
- }
- final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
- return mRotation == other.mRotation
- && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
- && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
- }
-
- @Override
- public String toString() {
- return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
- + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
- + " cutout=" + mRotatedDisplayCutout + "}";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRotation);
- dest.writeInt(mAppWidth);
- dest.writeInt(mAppHeight);
- dest.writeTypedObject(
- new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
- }
-
- private FixedRotationAdjustments(Parcel in) {
- mRotation = in.readInt();
- mAppWidth = in.readInt();
- mAppHeight = in.readInt();
- final DisplayCutout.ParcelableWrapper cutoutWrapper =
- in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
- mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
- }
-
- public static final Creator<FixedRotationAdjustments> CREATOR =
- new Creator<FixedRotationAdjustments>() {
- public FixedRotationAdjustments createFromParcel(Parcel in) {
- return new FixedRotationAdjustments(in);
- }
-
- public FixedRotationAdjustments[] newArray(int size) {
- return new FixedRotationAdjustments[size];
- }
- };
+ && Objects.equals(daj.mConfiguration, mConfiguration);
}
}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 097d1d0..c87c13d 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -161,6 +161,11 @@
return mConnectedView.get();
}
+ private void clearConnectedView() {
+ mConnectedView = null;
+ mConnectionCount = 0;
+ }
+
/**
* Notify HandwritingInitiator that a new InputConnection is created.
* The caller of this method should guarantee that each onInputConnectionCreated call
@@ -169,6 +174,10 @@
* @see #onInputConnectionClosed(View)
*/
public void onInputConnectionCreated(@NonNull View view) {
+ if (!view.isAutoHandwritingEnabled()) {
+ clearConnectedView();
+ return;
+ }
final View connectedView = getConnectedView();
if (connectedView == view) {
++mConnectionCount;
@@ -187,15 +196,15 @@
*/
public void onInputConnectionClosed(@NonNull View view) {
final View connectedView = getConnectedView();
+ if (connectedView == null) return;
if (connectedView == view) {
--mConnectionCount;
if (mConnectionCount == 0) {
- mConnectedView = null;
+ clearConnectedView();
}
} else {
// Unexpected branch, set mConnectedView to null to avoid further problem.
- mConnectedView = null;
- mConnectionCount = 0;
+ clearConnectedView();
}
}
@@ -218,6 +227,12 @@
if (connectedView == null) {
return;
}
+
+ if (!connectedView.isAutoHandwritingEnabled()) {
+ clearConnectedView();
+ return;
+ }
+
final ViewParent viewParent = connectedView.getParent();
// Do a final check before startHandwriting.
if (viewParent != null && connectedView.isAttachedToWindow()) {
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
index fc9661a..bf72a30 100644
--- a/core/java/android/view/ISurfaceControlViewHost.aidl
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -17,6 +17,8 @@
package android.view;
import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.InsetsState;
/**
* API from content embedder back to embedded content in SurfaceControlViewHost
@@ -25,4 +27,5 @@
oneway interface ISurfaceControlViewHost {
void onConfigurationChanged(in Configuration newConfig);
void onDispatchDetachedFromWindow();
+ void onInsetsChanged(in InsetsState state, in Rect insetFrame);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ccf1e44..32054b1 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -298,7 +298,7 @@
*/
void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
in IBinder hostInputToken, int flags, int privateFlags, int type,
- out InputChannel outInputChannel);
+ in IBinder focusGrantToken, out InputChannel outInputChannel);
/**
* Update the flags on an input channel associated with a particular surface.
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 85a9dbd..b461faf 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -22,10 +22,12 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.view.InsetsState;
import java.util.Objects;
@@ -71,6 +73,16 @@
release();
});
}
+
+ @Override
+ public void onInsetsChanged(InsetsState state, Rect frame) {
+ if (mViewRoot != null) {
+ mViewRoot.mHandler.post(() -> {
+ mViewRoot.setOverrideInsetsFrame(frame);
+ });
+ }
+ mWm.setInsetsState(state);
+ }
}
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
@@ -274,7 +286,7 @@
public @Nullable SurfacePackage getSurfacePackage() {
if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
- mViewRoot.getInputToken(), mRemoteInterface);
+ mWm.getFocusGrantToken(), mRemoteInterface);
} else {
return null;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 93fdee0..7559273 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3517,6 +3517,7 @@
* 1 PFLAG4_DETACHED
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
* 1 PFLAG4_DRAG_A11Y_STARTED
+ * 1 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
* |-------|-------|-------|-------|
*/
@@ -3593,6 +3594,10 @@
*/
private static final int PFLAG4_DRAG_A11Y_STARTED = 0x000008000;
+ /**
+ * Indicates that the view enables auto handwriting initiation.
+ */
+ private static final int PFLAG4_AUTO_HANDWRITING_ENABLED = 0x000010000;
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -5321,6 +5326,7 @@
(TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
(PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
(IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
+ mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -6034,6 +6040,9 @@
case R.styleable.View_preferKeepClear:
setPreferKeepClear(a.getBoolean(attr, false));
break;
+ case R.styleable.View_autoHandwritingEnabled:
+ setAutoHandwritingEnabled(a.getBoolean(attr, true));
+ break;
}
}
@@ -31118,6 +31127,42 @@
}
/**
+ * Set whether this view enables automatic handwriting initiation.
+ *
+ * For a view with an active {@link InputConnection}, if auto handwriting is enabled then
+ * stylus movement within its view boundary will automatically trigger the handwriting mode.
+ * Check {@link android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)} for
+ * more details about handwriting mode.
+ *
+ * If the View wants to initiate handwriting mode by itself, it can set this field to
+ * {@code false} and call
+ * {@link android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)} when there
+ * is stylus movement detected.
+ *
+ * @see #onCreateInputConnection(EditorInfo)
+ * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
+ * @param enabled whether auto handwriting initiation is enabled for this view.
+ * @attr ref android.R.styleable#View_autoHandwritingEnabled
+ */
+ public void setAutoHandwritingEnabled(boolean enabled) {
+ if (enabled) {
+ mPrivateFlags4 |= PFLAG4_AUTO_HANDWRITING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_AUTO_HANDWRITING_ENABLED;
+ }
+ }
+
+ /**
+ * Return whether the View allows automatic handwriting initiation. Returns true if automatic
+ * handwriting initiation is enabled, and verse visa.
+ * @see #setAutoHandwritingEnabled(boolean)
+ */
+ public boolean isAutoHandwritingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_AUTO_HANDWRITING_ENABLED)
+ == PFLAG4_AUTO_HANDWRITING_ENABLED;
+ }
+
+ /**
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1496a4a..eaa12e5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -643,6 +643,7 @@
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ Rect mOverrideInsetsFrame;
final Rect mPendingBackDropFrame = new Rect();
@@ -8069,7 +8070,22 @@
private void setFrame(Rect frame) {
mWinFrame.set(frame);
- mInsetsController.onFrameChanged(frame);
+ mInsetsController.onFrameChanged(mOverrideInsetsFrame != null ?
+ mOverrideInsetsFrame : frame);
+ }
+
+ /**
+ * In the normal course of operations we compute insets relative to
+ * the frame returned from relayout window. In the case of
+ * SurfaceControlViewHost, this frame is in local coordinates
+ * instead of global coordinates. We support this override
+ * frame so we can allow SurfaceControlViewHost to set a frame
+ * to be used to calculate insets, without disturbing the main
+ * mFrame.
+ */
+ void setOverrideInsetsFrame(Rect frame) {
+ mOverrideInsetsFrame = new Rect(frame);
+ mInsetsController.onFrameChanged(mOverrideInsetsFrame);
}
/**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index aa9ea19..039b50a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -607,6 +607,17 @@
* @param hasCapture True if the window has pointer capture.
*/
default public void onPointerCaptureChanged(boolean hasCapture) { };
+
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+ *
+ * @param appearance The newly applied appearance.
+ * @hide
+ */
+ default void onSystemBarAppearanceChanged(
+ @WindowInsetsController.Appearance int appearance) {
+ }
}
/** @hide */
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 02c8945..115e9e8 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -163,5 +163,10 @@
public void onPointerCaptureChanged(boolean hasCapture) {
mWrapped.onPointerCaptureChanged(hasCapture);
}
+
+ @Override
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ mWrapped.onSystemBarAppearanceChanged(appearance);
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 3392edc..56f0915 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -21,11 +21,14 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
+import android.view.InsetsState;
+import android.view.IWindow;
import android.window.ClientWindowFrames;
import android.window.IOnBackInvokedCallback;
@@ -48,12 +51,14 @@
int mDisplayId;
IBinder mInputChannelToken;
Region mInputRegion;
+ IWindow mClient;
State(SurfaceControl sc, WindowManager.LayoutParams p, int displayId,
- IBinder inputChannelToken) {
+ IBinder inputChannelToken, IWindow client) {
mSurfaceControl = sc;
mParams.copyFrom(p);
mDisplayId = displayId;
mInputChannelToken = inputChannelToken;
+ mClient = client;
}
};
@@ -75,6 +80,8 @@
private final Configuration mConfiguration;
private final IWindowSession mRealWm;
private final IBinder mHostInputToken;
+ private final IBinder mFocusGrantToken = new Binder();
+ private InsetsState mInsetsState;
private int mForceHeight = -1;
private int mForceWidth = -1;
@@ -91,6 +98,10 @@
mConfiguration.setTo(configuration);
}
+ IBinder getFocusGrantToken() {
+ return mFocusGrantToken;
+ }
+
/**
* Utility API.
*/
@@ -153,10 +164,10 @@
mRealWm.grantInputChannel(displayId,
new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
- outInputChannel);
+ mFocusGrantToken, outInputChannel);
} else {
mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
- attrs.privateFlags, attrs.type, outInputChannel);
+ attrs.privateFlags, attrs.type, mFocusGrantToken, outInputChannel);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -164,7 +175,7 @@
}
final State state = new State(sc, attrs, displayId,
- outInputChannel != null ? outInputChannel.getToken() : null);
+ outInputChannel != null ? outInputChannel.getToken() : null, window);
synchronized (this) {
mStateForWindow.put(window.asBinder(), state);
}
@@ -312,6 +323,10 @@
}
}
+ if (mInsetsState != null) {
+ outInsetsState.set(mInsetsState);
+ }
+
// Include whether the window is in touch mode.
return isInTouchMode() ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
}
@@ -469,7 +484,7 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
- IBinder hostInputToken, int flags, int privateFlags, int type,
+ IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
InputChannel outInputChannel) {
}
@@ -501,4 +516,15 @@
public boolean dropForAccessibility(IWindow window, int x, int y) {
return false;
}
+
+ public void setInsetsState(InsetsState state) {
+ mInsetsState = state;
+ for (State s : mStateForWindow.values()) {
+ try {
+ s.mClient.insetsChanged(state, false, false);
+ } catch (RemoteException e) {
+ // Too bad
+ }
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index b85fe7c..78509fe 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -972,6 +972,13 @@
* {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
* once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
* 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
+ * filter-out others.
+ * It can be CPU intensive to include all, filtering specific info is recommended.
+ * </p>
*/
int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
@@ -983,17 +990,69 @@
* <p>
* This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
* </p>
+ * <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
+ * filter-out others.
+ * It can be CPU intensive to include all, filtering specific info is recommended.
+ * </p>
*/
int CURSOR_UPDATE_MONITOR = 1 << 1;
/**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * with new {@link EditorBoundsInfo} whenever cursor/anchor position is changed. To disable
+ * monitoring, call {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+ * <p>
+ * This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
+ * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+ * </p>
+ */
+ int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 1 << 2;
+
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * with new character bounds {@link CursorAnchorInfo#getCharacterBounds(int)} 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_EDITOR_BOUNDS},
+ * {@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;
+
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * with new Insertion marker info {@link CursorAnchorInfo#getInsertionMarkerFlags()},
+ * {@link CursorAnchorInfo#getInsertionMarkerBaseline()}, etc 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} and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
+ * and {@link #CURSOR_UPDATE_MONITOR}.
+ * </p>
+ */
+ int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4;
+
+ /**
* Called by the input method to ask the editor for calling back
* {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to
* notify cursor/anchor locations.
*
- * @param cursorUpdateMode {@link #CURSOR_UPDATE_IMMEDIATE} and/or
- * {@link #CURSOR_UPDATE_MONITOR}. Pass {@code 0} to disable the effect of
- * {@link #CURSOR_UPDATE_MONITOR}.
+ * @param cursorUpdateMode any combination of update modes and filters:
+ * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and date filters:
+ * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}.
+ * Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be
+ * rejected and method will return {@code false}.
* @return {@code true} if the request is scheduled. {@code false} to indicate that when the
* application will not call {@link InputMethodManager#updateCursorAnchorInfo(
* android.view.View, CursorAnchorInfo)}.
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index fda72d5..ff6903e8 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -405,7 +405,15 @@
* @hide
*/
default void startStylusHandwriting(
- @NonNull InputChannel channel, @Nullable List<MotionEvent> events) {
+ int requestId, @NonNull InputChannel channel, @Nullable List<MotionEvent> events) {
+ // intentionally empty
+ }
+
+ /**
+ * Initialize Ink window early-on.
+ * @hide
+ */
+ default void initInkWindow() {
// intentionally empty
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 849f3c9..f480b24 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -1797,11 +1798,12 @@
}
if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
// TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
- // TODO (b/215533103): Introduce new modes in requestCursorUpdates().
// TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
// instead of mDisplayId.
mServedInputConnection.requestCursorUpdatesFromImm(
- CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, mDisplayId);
+ CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR
+ | CURSOR_UPDATE_FILTER_EDITOR_BOUNDS,
+ mDisplayId);
}
try {
@@ -2449,6 +2451,17 @@
}
/**
+ * Get the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ *
+ * @hide
+ */
+ public int getUpdateCursorAnchorInfoMode() {
+ synchronized (mH) {
+ return mRequestUpdateCursorAnchorInfoMonitorMode;
+ }
+ }
+
+ /**
* Report the current cursor location in its window.
*
* @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead.
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
index 48af7b9..aaeb120 100644
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
+++ b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
@@ -25,9 +25,8 @@
*/
oneway interface ISelectionToolbarCallback {
void onShown(in WidgetInfo info);
- void onHidden(long widgetToken);
- void onDismissed(long widgetToken);
void onWidgetUpdated(in WidgetInfo info);
+ void onToolbarShowTimeout();
void onMenuItemClicked(in ToolbarMenuItem item);
void onError(int errorCode);
}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.java b/core/java/android/view/selectiontoolbar/SelectionContext.java
deleted file mode 100644
index 21b8d8f..0000000
--- a/core/java/android/view/selectiontoolbar/SelectionContext.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The class holds information for a selection.
- *
- * @hide
- */
-@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
-public final class SelectionContext implements Parcelable {
-
- /**
- * The start index of a selection.
- */
- private final int mStartIndex;
-
- /**
- * The end index of a selection.
- */
- private final int mEndIndex;
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/SelectionContext.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 */ SelectionContext(
- int startIndex,
- int endIndex) {
- this.mStartIndex = startIndex;
- this.mEndIndex = endIndex;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * The start index of a selection.
- */
- @DataClass.Generated.Member
- public int getStartIndex() {
- return mStartIndex;
- }
-
- /**
- * The end index of a selection.
- */
- @DataClass.Generated.Member
- public int getEndIndex() {
- return mEndIndex;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "SelectionContext { " +
- "startIndex = " + mStartIndex + ", " +
- "endIndex = " + mEndIndex +
- " }";
- }
-
- @Override
- @DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(SelectionContext other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- SelectionContext that = (SelectionContext) o;
- //noinspection PointlessBooleanExpression
- return true
- && mStartIndex == that.mStartIndex
- && mEndIndex == that.mEndIndex;
- }
-
- @Override
- @DataClass.Generated.Member
- public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
- int _hash = 1;
- _hash = 31 * _hash + mStartIndex;
- _hash = 31 * _hash + mEndIndex;
- return _hash;
- }
-
- @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) { ... }
-
- dest.writeInt(mStartIndex);
- dest.writeInt(mEndIndex);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ SelectionContext(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- int startIndex = in.readInt();
- int endIndex = in.readInt();
-
- this.mStartIndex = startIndex;
- this.mEndIndex = endIndex;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<SelectionContext> CREATOR
- = new Parcelable.Creator<SelectionContext>() {
- @Override
- public SelectionContext[] newArray(int size) {
- return new SelectionContext[size];
- }
-
- @Override
- public SelectionContext createFromParcel(@NonNull android.os.Parcel in) {
- return new SelectionContext(in);
- }
- };
-
- /**
- * A builder for {@link SelectionContext}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static final class Builder {
-
- private int mStartIndex;
- private int mEndIndex;
-
- private long mBuilderFieldsSet = 0L;
-
- /**
- * Creates a new Builder.
- *
- * @param startIndex
- * The start index of a selection.
- * @param endIndex
- * The end index of a selection.
- */
- public Builder(
- int startIndex,
- int endIndex) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
- }
-
- /**
- * The start index of a selection.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setStartIndex(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mStartIndex = value;
- return this;
- }
-
- /**
- * The end index of a selection.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setEndIndex(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mEndIndex = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull SelectionContext build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
-
- SelectionContext o = new SelectionContext(
- mStartIndex,
- mEndIndex);
- 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 = 1639488292248L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/SelectionContext.java",
- inputSignatures = "private final int mStartIndex\nprivate final int mEndIndex\nclass SelectionContext extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
index ba45b85..6de0316 100644
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
@@ -47,6 +47,16 @@
private static final String REMOTE_SELECTION_TOOLBAR_ENABLED =
"remote_selection_toolbar_enabled";
+ /**
+ * Used to mark a toolbar that has no toolbar token id.
+ */
+ public static final long NO_TOOLBAR_ID = 0;
+
+ /**
+ * The error code that do not allow to create multiple toolbar.
+ */
+ public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1;
+
@NonNull
private final Context mContext;
private final ISelectionToolbarManager mService;
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
index bbbd5c0..594b6bc 100644
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -17,10 +17,14 @@
package android.view.selectiontoolbar;
import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.os.IBinder;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
+import java.util.List;
+
/**
* The class holds menu information for render service to render the selection toolbar.
@@ -29,14 +33,47 @@
*/
@DataClass(genToString = true, genEqualsHashCode = true)
public final class ShowInfo implements Parcelable {
+
/**
* The token that is used to identify the selection toolbar. This is initially set to 0
* until a selection toolbar has been created for the showToolbar request.
*/
private final long mWidgetToken;
- // TODO: add members when the code really uses it
+ /**
+ * If the toolbar menu items need to be re-layout.
+ */
+ private final boolean mLayoutRequired;
+ /**
+ * The menu items to be rendered in the selection toolbar.
+ */
+ @NonNull
+ private final List<ToolbarMenuItem> mMenuItems;
+
+ /**
+ * A rect specifying where the selection toolbar on the screen.
+ */
+ @NonNull
+ private final Rect mContentRect;
+
+ /**
+ * A recommended maximum suggested width of the selection toolbar.
+ */
+ private final int mSuggestedWidth;
+
+ /**
+ * The portion of the screen that is available to the selection toolbar.
+ */
+ @NonNull
+ private final Rect mViewPortOnScreen;
+
+ /**
+ * The host application's input token, this allows the remote render service to transfer
+ * the touch focus to the host application.
+ */
+ @NonNull
+ private final IBinder mHostInputToken;
@@ -57,24 +94,108 @@
* Creates a new ShowInfo.
*
* @param widgetToken
- * The token that is used to identify the selection toolbar.
+ * The token that is used to identify the selection toolbar. This is initially set to 0
+ * until a selection toolbar has been created for the showToolbar request.
+ * @param layoutRequired
+ * If the toolbar menu items need to be re-layout.
+ * @param menuItems
+ * The menu items to be rendered in the selection toolbar.
+ * @param contentRect
+ * A rect specifying where the selection toolbar on the screen.
+ * @param suggestedWidth
+ * A recommended maximum suggested width of the selection toolbar.
+ * @param viewPortOnScreen
+ * The portion of the screen that is available to the selection toolbar.
+ * @param hostInputToken
+ * The host application's input token, this allows the remote render service to transfer
+ * the touch focus to the host application.
*/
@DataClass.Generated.Member
public ShowInfo(
- long widgetToken) {
+ long widgetToken,
+ boolean layoutRequired,
+ @NonNull List<ToolbarMenuItem> menuItems,
+ @NonNull Rect contentRect,
+ int suggestedWidth,
+ @NonNull Rect viewPortOnScreen,
+ @NonNull IBinder hostInputToken) {
this.mWidgetToken = widgetToken;
+ this.mLayoutRequired = layoutRequired;
+ this.mMenuItems = menuItems;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMenuItems);
+ this.mContentRect = contentRect;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mContentRect);
+ this.mSuggestedWidth = suggestedWidth;
+ this.mViewPortOnScreen = viewPortOnScreen;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mViewPortOnScreen);
+ this.mHostInputToken = hostInputToken;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostInputToken);
// onConstructed(); // You can define this method to get a callback
}
/**
- * The token that is used to identify the selection toolbar.
+ * The token that is used to identify the selection toolbar. This is initially set to 0
+ * until a selection toolbar has been created for the showToolbar request.
*/
@DataClass.Generated.Member
public long getWidgetToken() {
return mWidgetToken;
}
+ /**
+ * If the toolbar menu items need to be re-layout.
+ */
+ @DataClass.Generated.Member
+ public boolean isLayoutRequired() {
+ return mLayoutRequired;
+ }
+
+ /**
+ * The menu items to be rendered in the selection toolbar.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<ToolbarMenuItem> getMenuItems() {
+ return mMenuItems;
+ }
+
+ /**
+ * A rect specifying where the selection toolbar on the screen.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Rect getContentRect() {
+ return mContentRect;
+ }
+
+ /**
+ * A recommended maximum suggested width of the selection toolbar.
+ */
+ @DataClass.Generated.Member
+ public int getSuggestedWidth() {
+ return mSuggestedWidth;
+ }
+
+ /**
+ * The portion of the screen that is available to the selection toolbar.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Rect getViewPortOnScreen() {
+ return mViewPortOnScreen;
+ }
+
+ /**
+ * The host application's input token, this allows the remote render service to transfer
+ * the touch focus to the host application.
+ */
+ @DataClass.Generated.Member
+ public @NonNull IBinder getHostInputToken() {
+ return mHostInputToken;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -82,7 +203,13 @@
// String fieldNameToString() { ... }
return "ShowInfo { " +
- "widgetToken = " + mWidgetToken +
+ "widgetToken = " + mWidgetToken + ", " +
+ "layoutRequired = " + mLayoutRequired + ", " +
+ "menuItems = " + mMenuItems + ", " +
+ "contentRect = " + mContentRect + ", " +
+ "suggestedWidth = " + mSuggestedWidth + ", " +
+ "viewPortOnScreen = " + mViewPortOnScreen + ", " +
+ "hostInputToken = " + mHostInputToken +
" }";
}
@@ -99,7 +226,13 @@
ShowInfo that = (ShowInfo) o;
//noinspection PointlessBooleanExpression
return true
- && mWidgetToken == that.mWidgetToken;
+ && mWidgetToken == that.mWidgetToken
+ && mLayoutRequired == that.mLayoutRequired
+ && java.util.Objects.equals(mMenuItems, that.mMenuItems)
+ && java.util.Objects.equals(mContentRect, that.mContentRect)
+ && mSuggestedWidth == that.mSuggestedWidth
+ && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
+ && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
}
@Override
@@ -110,6 +243,12 @@
int _hash = 1;
_hash = 31 * _hash + Long.hashCode(mWidgetToken);
+ _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
+ _hash = 31 * _hash + mSuggestedWidth;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
return _hash;
}
@@ -119,7 +258,15 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (mLayoutRequired) flg |= 0x2;
+ dest.writeByte(flg);
dest.writeLong(mWidgetToken);
+ dest.writeParcelableList(mMenuItems, flags);
+ dest.writeTypedObject(mContentRect, flags);
+ dest.writeInt(mSuggestedWidth);
+ dest.writeTypedObject(mViewPortOnScreen, flags);
+ dest.writeStrongBinder(mHostInputToken);
}
@Override
@@ -133,9 +280,31 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
+ boolean layoutRequired = (flg & 0x2) != 0;
long widgetToken = in.readLong();
+ List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
+ in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader());
+ Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
+ int suggestedWidth = in.readInt();
+ Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR);
+ IBinder hostInputToken = (IBinder) in.readStrongBinder();
this.mWidgetToken = widgetToken;
+ this.mLayoutRequired = layoutRequired;
+ this.mMenuItems = menuItems;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMenuItems);
+ this.mContentRect = contentRect;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mContentRect);
+ this.mSuggestedWidth = suggestedWidth;
+ this.mViewPortOnScreen = viewPortOnScreen;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mViewPortOnScreen);
+ this.mHostInputToken = hostInputToken;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostInputToken);
// onConstructed(); // You can define this method to get a callback
}
@@ -155,10 +324,10 @@
};
@DataClass.Generated(
- time = 1639488262761L,
+ time = 1643186262604L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
- inputSignatures = "private final long mWidgetToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
index 5af232c..89347c6 100644
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
+++ b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
import android.os.Parcelable;
+import android.view.MenuItem;
import com.android.internal.util.DataClass;
@@ -31,10 +33,84 @@
public final class ToolbarMenuItem implements Parcelable {
/**
+ * The priority of menu item is unknown.
+ */
+ public static final int PRIORITY_UNKNOWN = 0;
+
+ /**
+ * The priority of menu item is shown in primary selection toolbar.
+ */
+ public static final int PRIORITY_PRIMARY = 1;
+
+ /**
+ * The priority of menu item is shown in overflow selection toolbar.
+ */
+ public static final int PRIORITY_OVERFLOW = 2;
+
+ /**
* The id of the menu item.
+ *
+ * @see MenuItem#getItemId()
*/
private final int mItemId;
+ /**
+ * The title of the menu item.
+ *
+ * @see MenuItem#getTitle()
+ */
+ @NonNull
+ private final CharSequence mTitle;
+
+ /**
+ * The content description of the menu item.
+ *
+ * @see MenuItem#getContentDescription()
+ */
+ @Nullable
+ private final CharSequence mContentDescription;
+
+ /**
+ * The group id of the menu item.
+ *
+ * @see MenuItem#getGroupId()
+ */
+ private final int mGroupId;
+
+ /**
+ * The icon id of the menu item.
+ *
+ * @see MenuItem#getIcon()
+ */
+ @Nullable
+ private final Icon mIcon;
+
+ /**
+ * The tooltip text of the menu item.
+ *
+ * @see MenuItem#getTooltipText()
+ */
+ @Nullable
+ private final CharSequence mTooltipText;
+
+ /**
+ * The priority of the menu item used to display the order of the menu item.
+ */
+ private final int mPriority;
+
+ /**
+ * Returns the priority from a given {@link MenuItem}.
+ */
+ public static int getPriorityFromMenuItem(MenuItem menuItem) {
+ if (menuItem.requiresActionButton()) {
+ return PRIORITY_PRIMARY;
+ } else if (menuItem.requiresOverflow()) {
+ return PRIORITY_OVERFLOW;
+ }
+ return PRIORITY_UNKNOWN;
+ }
+
+
// Code below generated by codegen v1.0.23.
@@ -50,22 +126,118 @@
//@formatter:off
+ @android.annotation.IntDef(prefix = "PRIORITY_", value = {
+ PRIORITY_UNKNOWN,
+ PRIORITY_PRIMARY,
+ PRIORITY_OVERFLOW
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Priority {}
+
+ @DataClass.Generated.Member
+ public static String priorityToString(@Priority int value) {
+ switch (value) {
+ case PRIORITY_UNKNOWN:
+ return "PRIORITY_UNKNOWN";
+ case PRIORITY_PRIMARY:
+ return "PRIORITY_PRIMARY";
+ case PRIORITY_OVERFLOW:
+ return "PRIORITY_OVERFLOW";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ ToolbarMenuItem(
- int itemId) {
+ int itemId,
+ @NonNull CharSequence title,
+ @Nullable CharSequence contentDescription,
+ int groupId,
+ @Nullable Icon icon,
+ @Nullable CharSequence tooltipText,
+ int priority) {
this.mItemId = itemId;
+ this.mTitle = title;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTitle);
+ this.mContentDescription = contentDescription;
+ this.mGroupId = groupId;
+ this.mIcon = icon;
+ this.mTooltipText = tooltipText;
+ this.mPriority = priority;
// onConstructed(); // You can define this method to get a callback
}
/**
* The id of the menu item.
+ *
+ * @see MenuItem#getItemId()
*/
@DataClass.Generated.Member
public int getItemId() {
return mItemId;
}
+ /**
+ * The title of the menu item.
+ *
+ * @see MenuItem#getTitle()
+ */
+ @DataClass.Generated.Member
+ public @NonNull CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * The content description of the menu item.
+ *
+ * @see MenuItem#getContentDescription()
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * The group id of the menu item.
+ *
+ * @see MenuItem#getGroupId()
+ */
+ @DataClass.Generated.Member
+ public int getGroupId() {
+ return mGroupId;
+ }
+
+ /**
+ * The icon id of the menu item.
+ *
+ * @see MenuItem#getIcon()
+ */
+ @DataClass.Generated.Member
+ public @Nullable Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * The tooltip text of the menu item.
+ *
+ * @see MenuItem#getTooltipText()
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getTooltipText() {
+ return mTooltipText;
+ }
+
+ /**
+ * The priority of the menu item used to display the order of the menu item.
+ */
+ @DataClass.Generated.Member
+ public int getPriority() {
+ return mPriority;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -73,7 +245,13 @@
// String fieldNameToString() { ... }
return "ToolbarMenuItem { " +
- "itemId = " + mItemId +
+ "itemId = " + mItemId + ", " +
+ "title = " + mTitle + ", " +
+ "contentDescription = " + mContentDescription + ", " +
+ "groupId = " + mGroupId + ", " +
+ "icon = " + mIcon + ", " +
+ "tooltipText = " + mTooltipText + ", " +
+ "priority = " + mPriority +
" }";
}
@@ -90,7 +268,13 @@
ToolbarMenuItem that = (ToolbarMenuItem) o;
//noinspection PointlessBooleanExpression
return true
- && mItemId == that.mItemId;
+ && mItemId == that.mItemId
+ && java.util.Objects.equals(mTitle, that.mTitle)
+ && java.util.Objects.equals(mContentDescription, that.mContentDescription)
+ && mGroupId == that.mGroupId
+ && java.util.Objects.equals(mIcon, that.mIcon)
+ && java.util.Objects.equals(mTooltipText, that.mTooltipText)
+ && mPriority == that.mPriority;
}
@Override
@@ -101,6 +285,12 @@
int _hash = 1;
_hash = 31 * _hash + mItemId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription);
+ _hash = 31 * _hash + mGroupId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIcon);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText);
+ _hash = 31 * _hash + mPriority;
return _hash;
}
@@ -110,7 +300,18 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (mContentDescription != null) flg |= 0x4;
+ if (mIcon != null) flg |= 0x10;
+ if (mTooltipText != null) flg |= 0x20;
+ dest.writeByte(flg);
dest.writeInt(mItemId);
+ dest.writeCharSequence(mTitle);
+ if (mContentDescription != null) dest.writeCharSequence(mContentDescription);
+ dest.writeInt(mGroupId);
+ if (mIcon != null) dest.writeTypedObject(mIcon, flags);
+ if (mTooltipText != null) dest.writeCharSequence(mTooltipText);
+ dest.writeInt(mPriority);
}
@Override
@@ -124,9 +325,24 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
int itemId = in.readInt();
+ CharSequence title = (CharSequence) in.readCharSequence();
+ CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence();
+ int groupId = in.readInt();
+ Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR);
+ CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence();
+ int priority = in.readInt();
this.mItemId = itemId;
+ this.mTitle = title;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTitle);
+ this.mContentDescription = contentDescription;
+ this.mGroupId = groupId;
+ this.mIcon = icon;
+ this.mTooltipText = tooltipText;
+ this.mPriority = priority;
// onConstructed(); // You can define this method to get a callback
}
@@ -153,6 +369,12 @@
public static final class Builder {
private int mItemId;
+ private @NonNull CharSequence mTitle;
+ private @Nullable CharSequence mContentDescription;
+ private int mGroupId;
+ private @Nullable Icon mIcon;
+ private @Nullable CharSequence mTooltipText;
+ private int mPriority;
private long mBuilderFieldsSet = 0L;
@@ -161,14 +383,42 @@
*
* @param itemId
* The id of the menu item.
+ * @param title
+ * The title of the menu item.
+ * @param contentDescription
+ * The content description of the menu item.
+ * @param groupId
+ * The group id of the menu item.
+ * @param icon
+ * The icon id of the menu item.
+ * @param tooltipText
+ * The tooltip text of the menu item.
+ * @param priority
+ * The priority of the menu item used to display the order of the menu item.
*/
public Builder(
- int itemId) {
+ int itemId,
+ @NonNull CharSequence title,
+ @Nullable CharSequence contentDescription,
+ int groupId,
+ @Nullable Icon icon,
+ @Nullable CharSequence tooltipText,
+ int priority) {
mItemId = itemId;
+ mTitle = title;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTitle);
+ mContentDescription = contentDescription;
+ mGroupId = groupId;
+ mIcon = icon;
+ mTooltipText = tooltipText;
+ mPriority = priority;
}
/**
* The id of the menu item.
+ *
+ * @see MenuItem#getItemId()
*/
@DataClass.Generated.Member
public @NonNull Builder setItemId(int value) {
@@ -178,18 +428,100 @@
return this;
}
+ /**
+ * The title of the menu item.
+ *
+ * @see MenuItem#getTitle()
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTitle(@NonNull CharSequence value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTitle = value;
+ return this;
+ }
+
+ /**
+ * The content description of the menu item.
+ *
+ * @see MenuItem#getContentDescription()
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setContentDescription(@NonNull CharSequence value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mContentDescription = value;
+ return this;
+ }
+
+ /**
+ * The group id of the menu item.
+ *
+ * @see MenuItem#getGroupId()
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setGroupId(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mGroupId = value;
+ return this;
+ }
+
+ /**
+ * The icon id of the menu item.
+ *
+ * @see MenuItem#getIcon()
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIcon(@NonNull Icon value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mIcon = value;
+ return this;
+ }
+
+ /**
+ * The tooltip text of the menu item.
+ *
+ * @see MenuItem#getTooltipText()
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTooltipText(@NonNull CharSequence value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mTooltipText = value;
+ return this;
+ }
+
+ /**
+ * The priority of the menu item used to display the order of the menu item.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPriority(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40;
+ mPriority = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ToolbarMenuItem build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x2; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
ToolbarMenuItem o = new ToolbarMenuItem(
- mItemId);
+ mItemId,
+ mTitle,
+ mContentDescription,
+ mGroupId,
+ mIcon,
+ mTooltipText,
+ mPriority);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x2) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -197,10 +529,10 @@
}
@DataClass.Generated(
- time = 1639488328542L,
+ time = 1643200806234L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java",
- inputSignatures = "private final int mItemId\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int PRIORITY_UNKNOWN\npublic static final int PRIORITY_PRIMARY\npublic static final int PRIORITY_OVERFLOW\nprivate final int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final int mPriority\npublic static int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java
index 961d8ac..5d0fd47 100644
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.java
+++ b/core/java/android/view/selectiontoolbar/WidgetInfo.java
@@ -17,7 +17,10 @@
package android.view.selectiontoolbar;
import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControlViewHost;
import com.android.internal.util.DataClass;
@@ -35,7 +38,18 @@
*/
private final long mWidgetToken;
- // TODO: add members when the code really uses it
+ /**
+ * A Rect that defines the size and positioning of the remote view with respect to
+ * its host window.
+ */
+ @NonNull
+ private final Rect mContentRect;
+
+ /**
+ * The SurfacePackage pointing to the remote view.
+ */
+ @NonNull
+ private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@@ -57,11 +71,24 @@
*
* @param widgetToken
* The token that is used to identify the selection toolbar.
+ * @param contentRect
+ * A Rect that defines the size and positioning of the remote view with respect to
+ * its host window.
+ * @param surfacePackage
+ * The SurfacePackage pointing to the remote view.
*/
@DataClass.Generated.Member
public WidgetInfo(
- long widgetToken) {
+ long widgetToken,
+ @NonNull Rect contentRect,
+ @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
this.mWidgetToken = widgetToken;
+ this.mContentRect = contentRect;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mContentRect);
+ this.mSurfacePackage = surfacePackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSurfacePackage);
// onConstructed(); // You can define this method to get a callback
}
@@ -74,6 +101,23 @@
return mWidgetToken;
}
+ /**
+ * A Rect that defines the size and positioning of the remote view with respect to
+ * its host window.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Rect getContentRect() {
+ return mContentRect;
+ }
+
+ /**
+ * The SurfacePackage pointing to the remote view.
+ */
+ @DataClass.Generated.Member
+ public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
+ return mSurfacePackage;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -81,7 +125,9 @@
// String fieldNameToString() { ... }
return "WidgetInfo { " +
- "widgetToken = " + mWidgetToken +
+ "widgetToken = " + mWidgetToken + ", " +
+ "contentRect = " + mContentRect + ", " +
+ "surfacePackage = " + mSurfacePackage +
" }";
}
@@ -98,7 +144,9 @@
WidgetInfo that = (WidgetInfo) o;
//noinspection PointlessBooleanExpression
return true
- && mWidgetToken == that.mWidgetToken;
+ && mWidgetToken == that.mWidgetToken
+ && java.util.Objects.equals(mContentRect, that.mContentRect)
+ && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage);
}
@Override
@@ -109,16 +157,20 @@
int _hash = 1;
_hash = 31 * _hash + Long.hashCode(mWidgetToken);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage);
return _hash;
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeLong(mWidgetToken);
+ dest.writeTypedObject(mContentRect, flags);
+ dest.writeTypedObject(mSurfacePackage, flags);
}
@Override
@@ -128,13 +180,21 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ WidgetInfo(@NonNull android.os.Parcel in) {
+ /* package-private */ WidgetInfo(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
long widgetToken = in.readLong();
+ Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
+ SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
this.mWidgetToken = widgetToken;
+ this.mContentRect = contentRect;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mContentRect);
+ this.mSurfacePackage = surfacePackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSurfacePackage);
// onConstructed(); // You can define this method to get a callback
}
@@ -148,16 +208,16 @@
}
@Override
- public WidgetInfo createFromParcel(@NonNull android.os.Parcel in) {
+ public WidgetInfo createFromParcel(@NonNull Parcel in) {
return new WidgetInfo(in);
}
};
@DataClass.Generated(
- time = 1639488254020L,
+ time = 1643281495056L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java",
- inputSignatures = "private final long mWidgetToken\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "private final long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 90d44db..c29e774 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -18,11 +18,14 @@
import android.content.Context;
import android.content.res.Resources;
+import android.util.PluralsMessageFormatter;
import com.android.icu.text.DateSorterBridge;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
/**
* Sorts dates into the following groups:
@@ -73,9 +76,12 @@
mLabels[0] = dateSorterBridge.getToday();
mLabels[1] = dateSorterBridge.getYesterday();
- int resId = com.android.internal.R.plurals.last_num_days;
- String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
- mLabels[2] = String.format(format, NUM_DAYS_AGO);
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", NUM_DAYS_AGO);
+ mLabels[2] = PluralsMessageFormatter.format(
+ resources,
+ arguments,
+ com.android.internal.R.string.last_num_days);
mLabels[3] = context.getString(com.android.internal.R.string.last_month);
mLabels[4] = context.getString(com.android.internal.R.string.older);
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index e011d51..b1d07f5 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -26,6 +26,7 @@
import android.text.Selection;
import android.text.Spannable;
import android.text.TextWatcher;
+import android.util.PluralsMessageFormatter;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -35,6 +36,11 @@
import android.widget.EditText;
import android.widget.TextView;
+import com.android.internal.R;
+
+import java.util.HashMap;
+import java.util.Map;
+
/**
* @hide
*/
@@ -180,9 +186,14 @@
if (mNumberOfMatches == 0) {
mMatches.setText(com.android.internal.R.string.no_matches);
} else {
- mMatches.setText(mResources.getQuantityString(
- com.android.internal.R.plurals.matches_found, mNumberOfMatches,
- mActiveMatchIndex + 1, mNumberOfMatches));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", mActiveMatchIndex + 1);
+ arguments.put("total", mNumberOfMatches);
+
+ mMatches.setText(PluralsMessageFormatter.format(
+ mResources,
+ arguments,
+ R.string.matches_found));
}
mMatches.setVisibility(View.VISIBLE);
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index f14c251..280e7df 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -141,7 +141,7 @@
* and WebView to attempt to darken web content by algorithmic darkening when
* appropriate.
*
- * Refer to {@link #setAllowAlgorithmicDarkening} for detail.
+ * Refer to {@link #setAlgorithmicDarkeningAllowed} for detail.
*
* @hide
*/
@@ -1558,7 +1558,7 @@
* {@code targetSdkVersion} ≥ {@link android.os.Build.VERSION_CODES#TIRAMISU}
* this API is a no-op and WebView will always use the dark style defined by web content
* authors if the app's theme is dark. To customize the behavior, refer to
- * {@link #setAllowAlgorithmicDarkening}.
+ * {@link #setAlgorithmicDarkeningAllowed}.
*/
public void setForceDark(@ForceDark int forceDark) {
// Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1604,7 +1604,7 @@
*
* @param allow allow algorithmic darkening or not.
*/
- public void setAllowAlgorithmicDarkening(boolean allow) {
+ public void setAlgorithmicDarkeningAllowed(boolean allow) {
// Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
}
@@ -1613,9 +1613,9 @@
* The default is false.
*
* @return if the algorithmic darkening is allowed or not.
- * @see #setAllowAlgorithmicDarkening
+ * @see #setAlgorithmicDarkeningAllowed
*/
- public boolean getAllowAlgorithmicDarkening() {
+ public boolean isAlgorithmicDarkeningAllowed() {
// Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
return false;
}
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 9555522..2c62647 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -33,6 +33,7 @@
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
+import android.util.PluralsMessageFormatter;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
@@ -48,6 +49,8 @@
import java.time.temporal.JulianFields;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
//
// TODO
@@ -260,19 +263,17 @@
return;
} else if (duration < HOUR_IN_MILLIS) {
count = (int)(duration / MINUTE_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.R.plurals.duration_minutes_shortest
- : com.android.internal.R.plurals.duration_minutes_shortest_future,
- count),
+ result = getContext().getResources().getString(past
+ ? com.android.internal.R.string.duration_minutes_shortest
+ : com.android.internal.R.string.duration_minutes_shortest_future,
count);
millisIncrease = MINUTE_IN_MILLIS;
} else if (duration < DAY_IN_MILLIS) {
count = (int)(duration / HOUR_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.R.plurals.duration_hours_shortest
- : com.android.internal.R.plurals.duration_hours_shortest_future,
- count),
- count);
+ result = getContext().getResources().getString(past
+ ? com.android.internal.R.string.duration_hours_shortest
+ : com.android.internal.R.string.duration_hours_shortest_future,
+ count);
millisIncrease = HOUR_IN_MILLIS;
} else if (duration < YEAR_IN_MILLIS) {
// In weird cases it can become 0 because of daylight savings
@@ -281,10 +282,9 @@
LocalDateTime localNow = toLocalDateTime(now, zoneId);
count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.R.plurals.duration_days_shortest
- : com.android.internal.R.plurals.duration_days_shortest_future,
- count),
+ result = getContext().getResources().getString(past
+ ? com.android.internal.R.string.duration_days_shortest
+ : com.android.internal.R.string.duration_days_shortest_future,
count);
if (past || count != 1) {
mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
@@ -295,10 +295,9 @@
} else {
count = (int)(duration / YEAR_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.R.plurals.duration_years_shortest
- : com.android.internal.R.plurals.duration_years_shortest_future,
- count),
+ result = getContext().getResources().getString(past
+ ? com.android.internal.R.string.duration_years_shortest
+ : com.android.internal.R.string.duration_years_shortest_future,
count);
millisIncrease = YEAR_IN_MILLIS;
}
@@ -363,26 +362,25 @@
int count;
boolean past = (now >= mTimeMillis);
String result;
+ Map<String, Object> arguments = new HashMap<>();
if (duration < MINUTE_IN_MILLIS) {
result = mNowText;
} else if (duration < HOUR_IN_MILLIS) {
count = (int)(duration / MINUTE_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.
- R.plurals.duration_minutes_relative
- : com.android.internal.
- R.plurals.duration_minutes_relative_future,
- count),
- count);
+ arguments.put("count", count);
+ result = PluralsMessageFormatter.format(
+ getContext().getResources(),
+ arguments,
+ past ? R.string.duration_minutes_relative
+ : R.string.duration_minutes_relative_future);
} else if (duration < DAY_IN_MILLIS) {
count = (int)(duration / HOUR_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.
- R.plurals.duration_hours_relative
- : com.android.internal.
- R.plurals.duration_hours_relative_future,
- count),
- count);
+ arguments.put("count", count);
+ result = PluralsMessageFormatter.format(
+ getContext().getResources(),
+ arguments,
+ past ? R.string.duration_hours_relative
+ : R.string.duration_hours_relative_future);
} else if (duration < YEAR_IN_MILLIS) {
// In weird cases it can become 0 because of daylight savings
LocalDateTime localDateTime = mLocalTime;
@@ -390,23 +388,20 @@
LocalDateTime localNow = toLocalDateTime(now, zoneId);
count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.
- R.plurals.duration_days_relative
- : com.android.internal.
- R.plurals.duration_days_relative_future,
- count),
- count);
-
+ arguments.put("count", count);
+ result = PluralsMessageFormatter.format(
+ getContext().getResources(),
+ arguments,
+ past ? R.string.duration_days_relative
+ : R.string.duration_days_relative_future);
} else {
count = (int)(duration / YEAR_IN_MILLIS);
- result = String.format(getContext().getResources().getQuantityString(past
- ? com.android.internal.
- R.plurals.duration_years_relative
- : com.android.internal.
- R.plurals.duration_years_relative_future,
- count),
- count);
+ arguments.put("count", count);
+ result = PluralsMessageFormatter.format(
+ getContext().getResources(),
+ arguments,
+ past ? R.string.duration_years_relative
+ : R.string.duration_years_relative_future);
}
info.setText(result);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index dd70d69..3a7a544 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4567,6 +4567,19 @@
if (layout == null) {
return;
}
+ int mode = imm.getUpdateCursorAnchorInfoMode();
+ boolean includeEditorBounds =
+ (mode & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
+ boolean includeCharacterBounds =
+ (mode & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
+ boolean includeInsertionMarker =
+ (mode & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
+ boolean includeAll =
+ (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker);
+
+ includeEditorBounds |= includeAll;
+ includeCharacterBounds |= includeAll;
+ includeInsertionMarker |= includeAll;
final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder;
builder.reset();
@@ -4579,68 +4592,79 @@
mTextView.getLocationOnScreen(mTmpIntOffset);
mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]);
builder.setMatrix(mViewToScreenMatrix);
- final RectF bounds = new RectF();
- mTextView.getBoundsOnScreen(bounds, false /* clipToParent */);
- EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
- //TODO(b/210039666): add Handwriting bounds once they're available.
- builder.setEditorBoundsInfo(
- boundsBuilder.setEditorBounds(bounds).build());
- final float viewportToContentHorizontalOffset =
- mTextView.viewportToContentHorizontalOffset();
- final float viewportToContentVerticalOffset =
- mTextView.viewportToContentVerticalOffset();
-
- final CharSequence text = mTextView.getText();
- if (text instanceof Spannable) {
- final Spannable sp = (Spannable) text;
- int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
- int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
- if (composingTextEnd < composingTextStart) {
- final int temp = composingTextEnd;
- composingTextEnd = composingTextStart;
- composingTextStart = temp;
- }
- final boolean hasComposingText =
- (0 <= composingTextStart) && (composingTextStart < composingTextEnd);
- if (hasComposingText) {
- final CharSequence composingText = text.subSequence(composingTextStart,
- composingTextEnd);
- builder.setComposingText(composingTextStart, composingText);
- mTextView.populateCharacterBounds(builder, composingTextStart,
- composingTextEnd, viewportToContentHorizontalOffset,
- viewportToContentVerticalOffset);
- }
+ if (includeEditorBounds) {
+ final RectF bounds = new RectF();
+ mTextView.getBoundsOnScreen(bounds, false /* clipToParent */);
+ EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
+ //TODO(b/210039666): add Handwriting bounds once they're available.
+ builder.setEditorBoundsInfo(
+ boundsBuilder.setEditorBounds(bounds).build());
}
- // Treat selectionStart as the insertion point.
- if (0 <= selectionStart) {
- final int offset = selectionStart;
- final int line = layout.getLineForOffset(offset);
- final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
- + viewportToContentHorizontalOffset;
- final float insertionMarkerTop = layout.getLineTop(line)
- + viewportToContentVerticalOffset;
- final float insertionMarkerBaseline = layout.getLineBaseline(line)
- + viewportToContentVerticalOffset;
- final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line)
- + viewportToContentVerticalOffset;
- final boolean isTopVisible = mTextView
- .isPositionVisible(insertionMarkerX, insertionMarkerTop);
- final boolean isBottomVisible = mTextView
- .isPositionVisible(insertionMarkerX, insertionMarkerBottom);
- int insertionMarkerFlags = 0;
- if (isTopVisible || isBottomVisible) {
- insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+ if (includeCharacterBounds || includeInsertionMarker) {
+ final float viewportToContentHorizontalOffset =
+ mTextView.viewportToContentHorizontalOffset();
+ final float viewportToContentVerticalOffset =
+ mTextView.viewportToContentVerticalOffset();
+
+ if (includeCharacterBounds) {
+ final CharSequence text = mTextView.getText();
+ if (text instanceof Spannable) {
+ final Spannable sp = (Spannable) text;
+ int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
+ int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
+ if (composingTextEnd < composingTextStart) {
+ final int temp = composingTextEnd;
+ composingTextEnd = composingTextStart;
+ composingTextStart = temp;
+ }
+ final boolean hasComposingText =
+ (0 <= composingTextStart) && (composingTextStart
+ < composingTextEnd);
+ if (hasComposingText) {
+ final CharSequence composingText = text.subSequence(composingTextStart,
+ composingTextEnd);
+ builder.setComposingText(composingTextStart, composingText);
+ mTextView.populateCharacterBounds(builder, composingTextStart,
+ composingTextEnd, viewportToContentHorizontalOffset,
+ viewportToContentVerticalOffset);
+ }
+ }
}
- if (!isTopVisible || !isBottomVisible) {
- insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+
+ if (includeInsertionMarker) {
+ // Treat selectionStart as the insertion point.
+ if (0 <= selectionStart) {
+ final int offset = selectionStart;
+ final int line = layout.getLineForOffset(offset);
+ final float insertionMarkerX = layout.getPrimaryHorizontal(offset)
+ + viewportToContentHorizontalOffset;
+ final float insertionMarkerTop = layout.getLineTop(line)
+ + viewportToContentVerticalOffset;
+ final float insertionMarkerBaseline = layout.getLineBaseline(line)
+ + viewportToContentVerticalOffset;
+ final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line)
+ + viewportToContentVerticalOffset;
+ final boolean isTopVisible = mTextView
+ .isPositionVisible(insertionMarkerX, insertionMarkerTop);
+ final boolean isBottomVisible = mTextView
+ .isPositionVisible(insertionMarkerX, insertionMarkerBottom);
+ int insertionMarkerFlags = 0;
+ if (isTopVisible || isBottomVisible) {
+ insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+ }
+ if (!isTopVisible || !isBottomVisible) {
+ insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+ }
+ if (layout.isRtlCharAt(offset)) {
+ insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+ }
+ builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
+ insertionMarkerBaseline, insertionMarkerBottom,
+ insertionMarkerFlags);
+ }
}
- if (layout.isRtlCharAt(offset)) {
- insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
- }
- builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
- insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags);
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 9a07975..3a3eb74 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ResourcesManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -99,6 +100,10 @@
if (shouldUpdateWindowMetricsBounds(config, newConfig)) {
return true;
}
+ // If the display rotation has changed, we also need to update resources.
+ if (isDisplayRotationChanged(config, newConfig)) {
+ return true;
+ }
return configChanged == null ? config.diff(newConfig) != 0 : configChanged;
}
@@ -129,4 +134,15 @@
return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
}
+
+ private static boolean isDisplayRotationChanged(@NonNull Configuration config,
+ @NonNull Configuration newConfig) {
+ final int origRot = config.windowConfiguration.getDisplayRotation();
+ final int newRot = newConfig.windowConfiguration.getDisplayRotation();
+ if (newRot == WindowConfiguration.ROTATION_UNDEFINED
+ || origRot == WindowConfiguration.ROTATION_UNDEFINED) {
+ return false;
+ }
+ return origRot != newRot;
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index be7388b..4ae6bf7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -90,6 +90,7 @@
import android.util.AttributeSet;
import android.util.HashedStringCache;
import android.util.Log;
+import android.util.PluralsMessageFormatter;
import android.util.Size;
import android.util.Slog;
import android.view.LayoutInflater;
@@ -216,6 +217,9 @@
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ private static final String PLURALS_COUNT = "count";
+ private static final String PLURALS_FILE_NAME = "file_name";
+
@VisibleForTesting
public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
@@ -1551,8 +1555,13 @@
} else {
FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
int remUriCount = uriCount - 1;
- String fileName = getResources().getQuantityString(R.plurals.file_count,
- remUriCount, fileInfo.name, remUriCount);
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put(PLURALS_COUNT, remUriCount);
+ arguments.put(PLURALS_FILE_NAME, fileInfo.name);
+ String fileName = PluralsMessageFormatter.format(
+ getResources(),
+ arguments,
+ R.string.file_count);
TextView fileNameView = contentPreviewLayout.findViewById(
R.id.content_preview_filename);
diff --git a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
index ce2d229..33209e1 100644
--- a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
+++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -27,6 +29,7 @@
import android.util.Log;
import android.view.View;
import android.widget.TextView;
+
import com.android.internal.R;
/**
@@ -48,6 +51,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
final Intent intent = getIntent();
mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT);
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 9985262..52d54cd 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -242,12 +242,14 @@
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
* @param callback Use this to report {@link HotwordDetectionService} status.
+ * @param detectorType Indicate which detector is used.
*/
void updateState(
in Identity originatorIdentity,
in PersistableBundle options,
in SharedMemory sharedMemory,
- in IHotwordRecognitionStatusCallback callback);
+ in IHotwordRecognitionStatusCallback callback,
+ int detectorType);
/**
* Requests to shutdown hotword detection service.
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 29bb311..630c271 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -211,7 +211,10 @@
// 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.
final int knownFlagMask = InputConnection.CURSOR_UPDATE_IMMEDIATE
- | InputConnection.CURSOR_UPDATE_MONITOR;
+ | InputConnection.CURSOR_UPDATE_MONITOR
+ | InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
+ | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
+ | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
final int unknownFlags = cursorUpdateMode & ~knownFlagMask;
if (unknownFlags != 0) {
if (DEBUG) {
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 08bc8c7..30853bc 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -43,4 +43,5 @@
void notifyUserActionAsync();
void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
void onStylusHandwritingReady(int requestId);
+ void finishStylusHandwriting(int requestId);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 7ebcc88..2a7e1dc 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -410,4 +410,21 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * IME notifies that the current handwriting session should be closed.
+ * @param requestId
+ */
+ @AnyThread
+ public void finishStylusHandwriting(int requestId) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.finishStylusHandwriting(requestId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/core/java/com/android/internal/logging/InstanceId.aidl
similarity index 72%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to core/java/com/android/internal/logging/InstanceId.aidl
index 5206831..19a6177 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/core/java/com/android/internal/logging/InstanceId.aidl
@@ -1,11 +1,11 @@
/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package com.android.internal.logging;
-/**
- * @hide
- */
-parcelable SelectionContext;
+parcelable InstanceId;
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 3b6f8f6..b79c0be 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -62,6 +62,7 @@
public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
+ public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -209,6 +210,12 @@
NotificationManager.IMPORTANCE_LOW);
channelsList.add(accessibilitySecurityPolicyChannel);
+ final NotificationChannel abusiveBackgroundAppsChannel = new NotificationChannel(
+ ABUSIVE_BACKGROUND_APPS,
+ context.getString(R.string.notification_channel_abusive_bg_apps),
+ NotificationManager.IMPORTANCE_LOW);
+ channelsList.add(abusiveBackgroundAppsChannel);
+
nm.createNotificationChannels(channelsList);
}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 9443070..d8e89b4 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -33,6 +33,11 @@
public class AmbientDisplayPowerCalculator extends PowerCalculator {
private final UsageBasedPowerEstimator[] mPowerEstimators;
+ @Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY;
+ }
+
public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
final int numDisplays = powerProfile.getNumDisplays();
mPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
index 2eab506..f9310b0 100644
--- a/core/java/com/android/internal/os/AudioPowerCalculator.java
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -44,6 +44,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final PowerAndDuration total = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java
index 8178529..71a1463 100644
--- a/core/java/com/android/internal/os/BatteryChargeCalculator.java
+++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -30,6 +31,12 @@
public class BatteryChargeCalculator extends PowerCalculator {
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ // Always apply this power calculator, no matter what power components were requested
+ return true;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
builder.setDischargePercentage(
@@ -51,7 +58,8 @@
builder.setDischargePercentage(
batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
.setDischargedPowerRange(dischargedPowerLowerBoundMah,
- dischargedPowerUpperBoundMah);
+ dischargedPowerUpperBoundMah)
+ .setDischargeDurationMs(batteryStats.getBatteryRealtime(rawRealtimeUs) / 1000);
final long batteryTimeRemainingMs = batteryStats.computeBatteryTimeRemaining(rawRealtimeUs);
if (batteryTimeRemainingMs != -1) {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 69b7b4e..e4d5fb7 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -181,9 +181,22 @@
getProcessForegroundTimeMs(uid, realtimeUs));
}
+ final int[] powerComponents = query.getPowerComponents();
final List<PowerCalculator> powerCalculators = getPowerCalculators();
for (int i = 0, count = powerCalculators.size(); i < count; i++) {
PowerCalculator powerCalculator = powerCalculators.get(i);
+ if (powerComponents != null) {
+ boolean include = false;
+ for (int j = 0; j < powerComponents.length; j++) {
+ if (powerCalculator.isPowerComponentSupported(powerComponents[j])) {
+ include = true;
+ break;
+ }
+ }
+ if (!include) {
+ continue;
+ }
+ }
powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
query);
}
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 20535d2..066ee84 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -64,6 +64,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_BLUETOOTH;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
if (!batteryStats.hasBluetoothActivityReporting()) {
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index ddcabe8..7bccab5 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -37,6 +37,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_CAMERA;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index ee614cd..6a96cfe 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -93,6 +93,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_CPU;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
double totalPowerMah = 0;
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index bb307a0..4cb7ef1 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -37,6 +37,11 @@
}
@Override
+ public boolean isPowerComponentSupported(int powerComponent) {
+ return false;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
double[] totalAppPowerMah = null;
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index 32df17c..7d3f962 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -35,6 +35,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_FLASHLIGHT;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index a508e03..a836ddb 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -44,6 +44,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_GNSS;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
double appsPowerMah = 0;
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
new file mode 100644
index 0000000..9be686a
--- /dev/null
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+/**
+ * "Backend" interface used by {@link android.os.BinaryTransparencyManager} to talk to the
+ * BinaryTransparencyService that actually implements the measurement and information aggregation
+ * functionality.
+ *
+ * @see BinaryTransparencyManager
+ */
+interface IBinaryTransparencyService {
+ String getSignedImageInfo();
+
+ Map getApexInfo();
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index d33a88d..46808f9 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -47,6 +47,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
diff --git a/core/java/com/android/internal/os/MediaPowerCalculator.java b/core/java/com/android/internal/os/MediaPowerCalculator.java
index e93d93c..fff96da 100644
--- a/core/java/com/android/internal/os/MediaPowerCalculator.java
+++ b/core/java/com/android/internal/os/MediaPowerCalculator.java
@@ -15,6 +15,7 @@
*/
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
/**
@@ -33,6 +34,12 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO
+ || powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+ }
+
+ @Override
protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
// Calculate audio power usage, an estimate based on the average power routed to different
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 09fd85e..0440a58 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -24,6 +24,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_MEMORY;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 28cc836..a1d5fc9 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -86,6 +86,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index fd1d86b..0b9773e 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -2,6 +2,7 @@
per-file *Zygote* = file:/ZYGOTE_OWNERS
per-file *Cpu* = file:CPU_OWNERS
per-file *Binder* = file:BINDER_OWNERS
+per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 8dd463c..7310314 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -37,6 +37,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_PHONE;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 93d562c..d0a83e7 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -36,6 +36,14 @@
protected static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
/**
+ * Returns true if this power calculator computes power/duration for the specified
+ * power component.
+ */
+ public abstract boolean isPowerComponentSupported(
+ @BatteryConsumer.PowerComponent int powerComponent);
+
+
+ /**
* Attributes the total amount of power used by this subsystem to various consumers such
* as apps.
*
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 2b63459..d989e2a 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -66,6 +66,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_SCREEN;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index 83e5b57..495a6d9 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -39,6 +39,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_SENSORS;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
double appsPowerMah = 0;
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index c527c06..d7872ba 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -62,6 +62,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID);
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 8e80286..b590bf7 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
@@ -34,6 +35,11 @@
public class UserPowerCalculator extends PowerCalculator {
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return true;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final int[] userIds = query.getUserIds();
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
index 47916a6..a222bcb 100644
--- a/core/java/com/android/internal/os/VideoPowerCalculator.java
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -41,6 +41,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final PowerAndDuration total = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index e0ef129..aa6a4f8 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -44,6 +44,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_WAKELOCK;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final PowerAndDuration result = new PowerAndDuration();
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 2a71ac6..77f15f1 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -82,6 +82,11 @@
}
@Override
+ public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_WIFI;
+ }
+
+ @Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index d7eeb7b..a50282e 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1034,6 +1034,12 @@
@Override
public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
updateColorViews(null /* insets */, true /* animate */);
+ if (mWindow != null) {
+ final Window.Callback callback = mWindow.getCallback();
+ if (callback != null) {
+ callback.onSystemBarAppearanceChanged(appearance);
+ }
+ }
}
@Override
diff --git a/core/java/com/android/internal/statusbar/ISessionListener.aidl b/core/java/com/android/internal/statusbar/ISessionListener.aidl
new file mode 100644
index 0000000..101a2d2
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/ISessionListener.aidl
@@ -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 permissons and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import com.android.internal.logging.InstanceId;
+
+/** {@hide} */
+oneway interface ISessionListener {
+ void onSessionStarted(int sessionType, in InstanceId instance);
+ void onSessionEnded(int sessionType, in InstanceId instance);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3c6b7ff..accb986 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -28,7 +28,9 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.internal.statusbar.StatusBarIcon;
@@ -178,4 +180,17 @@
* @hide
*/
int getNavBarModeOverride();
+
+ /**
+ * Register a listener for certain sessions. Each session may be guarded by its own permission.
+ */
+ void registerSessionListener(int sessionFlags, in ISessionListener listener);
+ void unregisterSessionListener(int sessionFlags, in ISessionListener listener);
+
+ /**
+ * Informs all registered listeners that a session has begun and has the following instanceId.
+ * Can only be set by callers with certain permission based on the session type being updated.
+ */
+ void onSessionStarted(int sessionType, in InstanceId instanceId);
+ void onSessionEnded(int sessionType, in InstanceId instanceId);
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 6a626ee..da24832 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -61,5 +61,8 @@
void canStartStylusHandwriting(int requestId);
- void startStylusHandwriting(in InputChannel channel, in List<MotionEvent> events);
+ void startStylusHandwriting(int requestId, in InputChannel channel,
+ in List<MotionEvent> events);
+
+ void initInkWindow();
}
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index f47700c..f7af67b 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -16,6 +16,7 @@
package com.android.internal.widget.floatingtoolbar;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.view.MenuItem;
@@ -84,7 +85,8 @@
* @see PopupWindow#setFocusable(boolean)
* @see PopupWindow.OnDismissListener
*/
- boolean setOutsideTouchable(boolean outsideTouchable, PopupWindow.OnDismissListener onDismiss);
+ boolean setOutsideTouchable(boolean outsideTouchable,
+ @Nullable PopupWindow.OnDismissListener onDismiss);
/**
* Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
index b3a8128..8c2eb10 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
@@ -16,13 +16,47 @@
package com.android.internal.widget.floatingtoolbar;
+import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
import android.view.MenuItem;
+import android.view.SurfaceView;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
import android.view.selectiontoolbar.SelectionToolbarManager;
+import android.view.selectiontoolbar.ShowInfo;
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+import android.widget.LinearLayout;
import android.widget.PopupWindow;
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -34,55 +68,495 @@
*/
public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
- private final SelectionToolbarManager mSelectionToolbarManager;
- // Parent for the popup window.
- private final View mParent;
+ private static final boolean DEBUG =
+ Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE);
- public RemoteFloatingToolbarPopup(Context context, View parent) {
- // TODO: implement it
- mParent = Objects.requireNonNull(parent);
- mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
+ private static final int TOOLBAR_STATE_SHOWN = 1;
+ private static final int TOOLBAR_STATE_HIDDEN = 2;
+ private static final int TOOLBAR_STATE_DISMISSED = 3;
+
+ @IntDef(prefix = {"TOOLBAR_STATE_"}, value = {
+ TOOLBAR_STATE_SHOWN,
+ TOOLBAR_STATE_HIDDEN,
+ TOOLBAR_STATE_DISMISSED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToolbarState {
}
+ @NonNull
+ private final SelectionToolbarManager mSelectionToolbarManager;
+ // Parent for the popup window.
+ @NonNull
+ private final View mParent;
+ // A popup window used for showing menu items rendered by the remote system process
+ @NonNull
+ private final PopupWindow mPopupWindow;
+ // The callback to handle remote rendered selection toolbar.
+ @NonNull
+ private final SelectionToolbarCallbackImpl mSelectionToolbarCallback;
+
+ // tracks this popup state.
+ private @ToolbarState int mState;
+
+ // The token of the current showing floating toolbar.
+ private long mFloatingToolbarToken;
+ private final Rect mPreviousContentRect = new Rect();
+ private List<MenuItem> mMenuItems;
+ private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
+ private int mSuggestedWidth;
+ private final Rect mScreenViewPort = new Rect();
+ private boolean mWidthChanged = true;
+
+ private final int[] mCoordsOnScreen = new int[2];
+ private final int[] mCoordsOnWindow = new int[2];
+
+ public RemoteFloatingToolbarPopup(Context context, View parent) {
+ mParent = Objects.requireNonNull(parent);
+ mPopupWindow = createPopupWindow(context);
+ mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
+ mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
+ mFloatingToolbarToken = NO_TOOLBAR_ID;
+ }
+
+ @UiThread
@Override
public void show(List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
- // TODO: implement it
+ Objects.requireNonNull(menuItems);
+ Objects.requireNonNull(menuItemClickListener);
+ if (isShowing() && Objects.equals(menuItems, mMenuItems)
+ && Objects.equals(contentRect, mPreviousContentRect)) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "Ignore duplicate show() for the same content.");
+ }
+ return;
+ }
+
+ boolean isLayoutRequired = mMenuItems == null
+ || !MenuItemRepr.reprEquals(menuItems, mMenuItems)
+ || mWidthChanged;
+ if (isLayoutRequired) {
+ mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
+ doDismissPopupWindow();
+ }
+ mMenuItemClickListener = menuItemClickListener;
+ mMenuItems = menuItems;
+
+ mParent.getWindowVisibleDisplayFrame(mScreenViewPort);
+ final int suggestWidth = mSuggestedWidth > 0
+ ? mSuggestedWidth
+ : mParent.getResources().getDimensionPixelSize(
+ R.dimen.floating_toolbar_preferred_width);
+ final ShowInfo showInfo = new ShowInfo(
+ mFloatingToolbarToken, isLayoutRequired,
+ getToolbarMenuItems(mMenuItems),
+ contentRect,
+ suggestWidth,
+ mScreenViewPort,
+ mParent.getViewRootImpl().getInputToken());
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "RemoteFloatingToolbarPopup.show() for " + showInfo);
+ }
+ mSelectionToolbarManager.showToolbar(showInfo, mSelectionToolbarCallback);
+ mPreviousContentRect.set(contentRect);
}
+ @UiThread
+ @Override
+ public void dismiss() {
+ if (mState == TOOLBAR_STATE_DISMISSED) {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "The floating toolbar already dismissed.");
+ return;
+ }
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "RemoteFloatingToolbarPopup.dismiss().");
+ }
+ mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
+ doDismissPopupWindow();
+ }
+
+ @UiThread
@Override
public void hide() {
- // TODO: implement it
+ if (mState == TOOLBAR_STATE_DISMISSED || mState == TOOLBAR_STATE_HIDDEN) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "The floating toolbar already dismissed or hidden.");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "RemoteFloatingToolbarPopup.hide().");
+ }
+ mSelectionToolbarManager.hideToolbar(mFloatingToolbarToken);
+ mState = TOOLBAR_STATE_HIDDEN;
+ mPopupWindow.dismiss();
}
+ @UiThread
@Override
public void setSuggestedWidth(int suggestedWidth) {
- // TODO: implement it
+ int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+ mWidthChanged = difference > (mSuggestedWidth * 0.2);
+ mSuggestedWidth = suggestedWidth;
}
@Override
public void setWidthChanged(boolean widthChanged) {
- // no-op
+ mWidthChanged = widthChanged;
}
- @Override
- public void dismiss() {
- // TODO: implement it
- }
-
+ @UiThread
@Override
public boolean isHidden() {
- return false;
+ return mState == TOOLBAR_STATE_HIDDEN;
}
+ @UiThread
@Override
public boolean isShowing() {
- return false;
+ return mState == TOOLBAR_STATE_SHOWN;
}
+ @UiThread
@Override
public boolean setOutsideTouchable(boolean outsideTouchable,
- PopupWindow.OnDismissListener onDismiss) {
- return false;
+ @Nullable PopupWindow.OnDismissListener onDismiss) {
+ if (mState == TOOLBAR_STATE_DISMISSED) {
+ return false;
+ }
+ boolean ret = false;
+ if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
+ mPopupWindow.setOutsideTouchable(outsideTouchable);
+ mPopupWindow.setFocusable(!outsideTouchable);
+ mPopupWindow.update();
+ ret = true;
+ }
+ mPopupWindow.setOnDismissListener(onDismiss);
+ return ret;
+ }
+
+ private void updatePopupWindowContent(WidgetInfo widgetInfo) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "updatePopupWindowContent.");
+ }
+ ViewGroup contentContainer = (ViewGroup) mPopupWindow.getContentView();
+ contentContainer.removeAllViews();
+ SurfaceView surfaceView = new SurfaceView(mParent.getContext());
+ surfaceView.setZOrderOnTop(true);
+ surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);
+ surfaceView.setChildSurfacePackage(widgetInfo.getSurfacePackage());
+ contentContainer.addView(surfaceView);
+ }
+
+ private MenuItem getMenuItemByToolbarMenuItem(ToolbarMenuItem toolbarMenuItem) {
+ for (MenuItem item : mMenuItems) {
+ if (toolbarMenuItem.getItemId() == item.getItemId()) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ private Point getCoordinatesInWindow(int x, int y) {
+ // We later specify the location of PopupWindow relative to the attached window.
+ // The idea here is that 1) we can get the location of a View in both window coordinates
+ // and screen coordinates, where the offset between them should be equal to the window
+ // origin, and 2) we can use an arbitrary for this calculation while calculating the
+ // location of the rootview is supposed to be least expensive.
+ // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
+ // the following calculation.
+ mParent.getRootView().getLocationOnScreen(mCoordsOnScreen);
+ mParent.getRootView().getLocationInWindow(mCoordsOnWindow);
+ int windowLeftOnScreen = mCoordsOnScreen[0] - mCoordsOnWindow[0];
+ int windowTopOnScreen = mCoordsOnScreen[1] - mCoordsOnWindow[1];
+ return new Point(Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
+ }
+
+ private static List<ToolbarMenuItem> getToolbarMenuItems(List<MenuItem> menuItems) {
+ final List<ToolbarMenuItem> list = new ArrayList<>(menuItems.size());
+ for (MenuItem menuItem : menuItems) {
+ // TODO: use ToolbarMenuItem.Builder(MenuItem) instead
+ ToolbarMenuItem toolbarMenuItem = new ToolbarMenuItem.Builder(menuItem.getItemId(),
+ menuItem.getTitle(), menuItem.getContentDescription(), menuItem.getGroupId(),
+ convertDrawableToIcon(menuItem.getIcon()),
+ menuItem.getTooltipText(),
+ ToolbarMenuItem.getPriorityFromMenuItem(menuItem)).build();
+ list.add(toolbarMenuItem);
+ }
+ return list;
+ }
+
+ private static Icon convertDrawableToIcon(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+ if (drawable instanceof BitmapDrawable) {
+ final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return Icon.createWithBitmap(bitmapDrawable.getBitmap());
+ }
+ }
+ final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return Icon.createWithBitmap(bitmap);
+ }
+
+ private static PopupWindow createPopupWindow(Context content) {
+ ViewGroup popupContentHolder = new LinearLayout(content);
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ popupWindow.setClippingEnabled(false);
+ popupWindow.setWindowLayoutType(
+ WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ return popupWindow;
+ }
+
+ private void doDismissPopupWindow() {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "RemoteFloatingToolbarPopup.doDismiss().");
+ }
+ mState = TOOLBAR_STATE_DISMISSED;
+ mMenuItems = null;
+ mMenuItemClickListener = null;
+ mFloatingToolbarToken = 0;
+ mSuggestedWidth = 0;
+ mWidthChanged = true;
+ resetCoords();
+ mPreviousContentRect.setEmpty();
+ mScreenViewPort.setEmpty();
+ mPopupWindow.dismiss();
+ }
+
+ private void resetCoords() {
+ mCoordsOnScreen[0] = 0;
+ mCoordsOnScreen[1] = 0;
+ mCoordsOnWindow[0] = 0;
+ mCoordsOnWindow[1] = 0;
+ }
+
+ private void runOnUiThread(Runnable runnable) {
+ mParent.post(runnable);
+ }
+
+ private void onShow(WidgetInfo info) {
+ runOnUiThread(() -> {
+ mFloatingToolbarToken = info.getWidgetToken();
+ mState = TOOLBAR_STATE_SHOWN;
+ updatePopupWindowContent(info);
+ Rect contentRect = info.getContentRect();
+ mPopupWindow.setWidth(contentRect.width());
+ mPopupWindow.setHeight(contentRect.height());
+ final Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
+ mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, coords.x, coords.y);
+ });
+ }
+
+ private void onWidgetUpdated(WidgetInfo info) {
+ runOnUiThread(() -> {
+ if (!isShowing()) {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "onWidgetUpdated(): The widget isn't showing.");
+ return;
+ }
+ updatePopupWindowContent(info);
+ Rect contentRect = info.getContentRect();
+ Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "PopupWindow x= " + coords.x + " y= " + coords.y + " w="
+ + contentRect.width() + " h=" + contentRect.height());
+ }
+ mPopupWindow.update(coords.x, coords.y, contentRect.width(), contentRect.height());
+ });
+ }
+
+ private void onToolbarShowTimeout() {
+ runOnUiThread(() -> {
+ if (mState == TOOLBAR_STATE_DISMISSED) {
+ return;
+ }
+ doDismissPopupWindow();
+ });
+ }
+
+ private void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
+ runOnUiThread(() -> {
+ if (mMenuItems == null || mMenuItemClickListener == null) {
+ return;
+ }
+ MenuItem item = getMenuItemByToolbarMenuItem(toolbarMenuItem);
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "SelectionToolbarCallbackImpl onMenuItemClicked. toolbarMenuItem="
+ + toolbarMenuItem + " item=" + item);
+ }
+ // TODO: handle the menu item like clipboard
+ if (item != null) {
+ mMenuItemClickListener.onMenuItemClick(item);
+ } else {
+ Log.e(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "onMenuItemClicked: cannot find menu item.");
+ }
+ });
+ }
+
+ private static class SelectionToolbarCallbackImpl extends ISelectionToolbarCallback.Stub {
+
+ private final WeakReference<RemoteFloatingToolbarPopup> mRemotePopup;
+
+ SelectionToolbarCallbackImpl(RemoteFloatingToolbarPopup popup) {
+ mRemotePopup = new WeakReference<>(popup);
+ }
+
+ @Override
+ public void onShown(WidgetInfo info) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "SelectionToolbarCallbackImpl onShown: " + info);
+ }
+ final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
+ if (remoteFloatingToolbarPopup != null) {
+ remoteFloatingToolbarPopup.onShow(info);
+ } else {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "Lost remoteFloatingToolbarPopup reference for onShown.");
+ }
+ }
+
+ @Override
+ public void onWidgetUpdated(WidgetInfo info) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "SelectionToolbarCallbackImpl onWidgetUpdated: info = " + info);
+ }
+ final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
+ if (remoteFloatingToolbarPopup != null) {
+ remoteFloatingToolbarPopup.onWidgetUpdated(info);
+ } else {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "Lost remoteFloatingToolbarPopup reference for onWidgetUpdated.");
+ }
+ }
+
+ @Override
+ public void onToolbarShowTimeout() {
+ final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
+ if (remoteFloatingToolbarPopup != null) {
+ remoteFloatingToolbarPopup.onToolbarShowTimeout();
+ } else {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "Lost remoteFloatingToolbarPopup reference for onToolbarShowTimeout.");
+ }
+ }
+
+ @Override
+ public void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
+ final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
+ if (remoteFloatingToolbarPopup != null) {
+ remoteFloatingToolbarPopup.onMenuItemClicked(toolbarMenuItem);
+ } else {
+ Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "Lost remoteFloatingToolbarPopup reference for onMenuItemClicked.");
+ }
+ }
+
+ @Override
+ public void onError(int errorCode) {
+ if (DEBUG) {
+ Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
+ "SelectionToolbarCallbackImpl onError: " + errorCode);
+ }
+ }
+ }
+
+ /**
+ * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
+ */
+ static final class MenuItemRepr {
+
+ public final int mItemId;
+ public final int mGroupId;
+ @Nullable
+ public final String mTitle;
+ @Nullable private final Drawable mIcon;
+
+ private MenuItemRepr(
+ int itemId, int groupId, @Nullable CharSequence title,
+ @Nullable Drawable icon) {
+ mItemId = itemId;
+ mGroupId = groupId;
+ mTitle = (title == null) ? null : title.toString();
+ mIcon = icon;
+ }
+
+ /**
+ * Creates an instance of MenuItemRepr for the specified menu item.
+ */
+ public static MenuItemRepr of(MenuItem menuItem) {
+ return new MenuItemRepr(
+ menuItem.getItemId(),
+ menuItem.getGroupId(),
+ menuItem.getTitle(),
+ menuItem.getIcon());
+ }
+
+ /**
+ * Returns this object's hashcode.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mItemId, mGroupId, mTitle, mIcon);
+ }
+
+ /**
+ * Returns true if this object is the same as the specified object.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof LocalFloatingToolbarPopup.MenuItemRepr)) {
+ return false;
+ }
+ final MenuItemRepr other = (MenuItemRepr) o;
+ return mItemId == other.mItemId
+ && mGroupId == other.mGroupId
+ && TextUtils.equals(mTitle, other.mTitle)
+ // Many Drawables (icons) do not implement equals(). Using equals() here instead
+ // of reference comparisons in case a Drawable subclass implements equals().
+ && Objects.equals(mIcon, other.mIcon);
+ }
+
+ /**
+ * Returns true if the two menu item collections are the same based on MenuItemRepr.
+ */
+ public static boolean reprEquals(
+ Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
+ if (menuItems1.size() != menuItems2.size()) {
+ return false;
+ }
+
+ final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
+ for (MenuItem menuItem1 : menuItems1) {
+ final MenuItem menuItem2 = menuItems2Iter.next();
+ if (!MenuItemRepr.of(menuItem1).equals(
+ MenuItemRepr.of(menuItem2))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
}
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index 845d65c..a022842 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -16,20 +16,19 @@
#define LOG_TAG "UsbDeviceConnectionJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
+#include <fcntl.h>
#include <nativehelper/JNIPlatformHelp.h>
-#include "core_jni_helpers.h"
-
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <usbhost/usbhost.h>
+#include <usbhost/usbhost_jni.h>
#include <chrono>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "utils/Log.h"
using namespace android;
using namespace std::chrono;
@@ -91,22 +90,8 @@
static jbyteArray
android_hardware_UsbDeviceConnection_get_desc(JNIEnv *env, jobject thiz)
{
- char buffer[16384];
int fd = android_hardware_UsbDeviceConnection_get_fd(env, thiz);
- if (fd < 0) return NULL;
- lseek(fd, 0, SEEK_SET);
- int length = read(fd, buffer, sizeof(buffer));
- if (length < 0) return NULL;
-
- jbyteArray ret = env->NewByteArray(length);
- if (ret) {
- jbyte* bytes = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
- if (bytes) {
- memcpy(bytes, buffer, length);
- env->ReleasePrimitiveArrayCritical(ret, bytes, 0);
- }
- }
- return ret;
+ return usb_jni_read_descriptors(env, fd);
}
static jboolean
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3e2b258..e13b788 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -1236,6 +1236,65 @@
return false;
}
+static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile,
+ const audio_profile *nAudioProfile, bool useInMask) {
+ size_t numPositionMasks = 0;
+ size_t numIndexMasks = 0;
+
+ // count up how many masks are positional and indexed
+ for (size_t index = 0; index < nAudioProfile->num_channel_masks; index++) {
+ const audio_channel_mask_t mask = nAudioProfile->channel_masks[index];
+ if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
+ numIndexMasks++;
+ } else {
+ numPositionMasks++;
+ }
+ }
+
+ ScopedLocalRef<jintArray> jSamplingRates(env,
+ env->NewIntArray(nAudioProfile->num_sample_rates));
+ ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
+ ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
+ if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+ return AUDIO_JAVA_ERROR;
+ }
+
+ if (nAudioProfile->num_sample_rates) {
+ env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, nAudioProfile->num_sample_rates,
+ (jint *)nAudioProfile->sample_rates);
+ }
+
+ // put the masks in the output arrays
+ for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
+ maskIndex < nAudioProfile->num_channel_masks; maskIndex++) {
+ const audio_channel_mask_t mask = nAudioProfile->channel_masks[maskIndex];
+ if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
+ jint jMask = audio_channel_mask_get_bits(mask);
+ env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask);
+ } else {
+ jint jMask = useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask);
+ env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask);
+ }
+ }
+
+ int encapsulationType;
+ if (audioEncapsulationTypeFromNative(nAudioProfile->encapsulation_type, &encapsulationType) !=
+ NO_ERROR) {
+ ALOGW("Unknown encapsulation type for JAVA API: %u", nAudioProfile->encapsulation_type);
+ }
+
+ *jAudioProfile =
+ env->NewObject(gAudioProfileClass, gAudioProfileCstor,
+ audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(),
+ jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType);
+
+ if (jAudioProfile == nullptr) {
+ return AUDIO_JAVA_ERROR;
+ }
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort,
const struct audio_port_v7 *nAudioPort) {
jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
@@ -2479,6 +2538,10 @@
return AudioSystem::isHapticPlaybackSupported();
}
+static jboolean android_media_AudioSystem_isUltrasoundSupported(JNIEnv *env, jobject thiz) {
+ return AudioSystem::isUltrasoundSupported();
+}
+
static jint android_media_AudioSystem_setSupportedSystemUsages(JNIEnv *env, jobject thiz,
jintArray systemUsages) {
std::vector<audio_usage_t> nativeSystemUsagesVector;
@@ -2814,6 +2877,50 @@
return convertAudioDirectModeFromNative(directMode);
}
+static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env, jobject thiz,
+ jobject jAudioAttributes,
+ jobject jAudioProfilesList) {
+ ALOGV("getDirectProfilesForAttributes");
+
+ if (jAudioAttributes == nullptr) {
+ ALOGE("jAudioAttributes is NULL");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (jAudioProfilesList == nullptr) {
+ ALOGE("jAudioProfilesList is NULL");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jAudioProfilesList, gArrayListClass)) {
+ ALOGE("jAudioProfilesList not an ArrayList");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+ jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+ if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+
+ std::vector<audio_profile> audioProfiles;
+ status_t status = AudioSystem::getDirectProfilesForAttributes(paa.get(), &audioProfiles);
+ if (status != NO_ERROR) {
+ ALOGE("AudioSystem::getDirectProfilesForAttributes error %d", status);
+ jStatus = nativeToJavaStatus(status);
+ return jStatus;
+ }
+
+ for (const auto &audioProfile : audioProfiles) {
+ jobject jAudioProfile;
+ jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile);
+ env->DeleteLocalRef(jAudioProfile);
+ }
+ return jStatus;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] =
@@ -2915,6 +3022,7 @@
{"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids},
{"isHapticPlaybackSupported", "()Z",
(void *)android_media_AudioSystem_isHapticPlaybackSupported},
+ {"isUltrasoundSupported", "()Z", (void *)android_media_AudioSystem_isUltrasoundSupported},
{"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
(void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia},
{"setSupportedSystemUsages", "([I)I",
@@ -2960,7 +3068,10 @@
(void *)android_media_AudioSystem_canBeSpatialized},
{"getDirectPlaybackSupport",
"(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
- (void *)android_media_AudioSystem_getDirectPlaybackSupport}};
+ (void *)android_media_AudioSystem_getDirectPlaybackSupport},
+ {"getDirectProfilesForAttributes",
+ "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
+ (void *)android_media_AudioSystem_getDirectProfilesForAttributes}};
static const JNINativeMethod gEventHandlerMethods[] = {
{"native_setup",
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index 81d849e..97870a1 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -22,6 +22,13 @@
import "frameworks/base/core/proto/android/privacy.proto";
+message AllSensorPrivacyServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Is global sensor privacy enabled
+ optional bool is_enabled = 1;
+}
+
message SensorPrivacyServiceDumpProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -35,6 +42,9 @@
// Per user settings for sensor privacy
repeated SensorPrivacyUserProto user = 3;
+
+ // Implementation
+ optional string storage_implementation = 4;
}
message SensorPrivacyUserProto {
@@ -43,16 +53,47 @@
// User id
optional int32 user_id = 1;
+ // DEPRECATED
// Is global sensor privacy enabled
optional bool is_enabled = 2;
// Per sensor privacy enabled
+ // DEPRECATED
repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3;
+
+ // Per toggle type sensor privacy
+ repeated SensorPrivacySensorProto sensors = 4;
+}
+
+message SensorPrivacySensorProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ enum Sensor {
+ UNKNOWN = 0;
+
+ MICROPHONE = 1;
+ CAMERA = 2;
+ }
+
+ optional int32 sensor = 1;
+
+ repeated SensorPrivacyIndividualEnabledSensorProto toggles = 2;
}
message SensorPrivacyIndividualEnabledSensorProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
+ enum ToggleType {
+ SOFTWARE = 1;
+ HARDWARE = 2;
+ }
+
+ enum StateType {
+ ENABLED = 1;
+ DISABLED = 2;
+ }
+
+ // DEPRECATED
enum Sensor {
UNKNOWN = 0;
@@ -63,8 +104,17 @@
// Sensor for which privacy might be enabled
optional Sensor sensor = 1;
- // If sensor privacy is enabled for this sensor
+ // DEPRECATED
optional bool is_enabled = 2;
+
+ // Timestamp of the last time the sensor was changed
+ optional int64 last_change = 3;
+
+ // The toggle type for this state
+ optional ToggleType toggle_type = 4;
+
+ // If sensor privacy state for this sensor
+ optional StateType state_type = 5;
}
message SensorPrivacyToggleSourceProto {
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index c0a9f03..cc90e05 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -98,4 +98,7 @@
// Sum of all discharge percentage point drops during the reported session.
optional int32 session_discharge_percentage = 6;
-}
\ No newline at end of file
+
+ // Total amount of time battery was discharging during the reported session
+ optional int64 discharge_duration_millis = 7;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c62d964..ff04339 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -715,8 +715,9 @@
<protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
<!-- Added in T -->
- <protected-broadcast android:name="android.intent.action.REFRESH_SAFETY_SOURCES" />
+ <protected-broadcast android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES" />
<protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
+ <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -3119,8 +3120,6 @@
({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
by {@link android.companion.CompanionDeviceManager}.
<p>Not for use by third-party applications.
- @hide
- @SystemApi
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
android:protectionLevel="signature|privileged" />
@@ -3130,15 +3129,20 @@
({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
by {@link android.companion.CompanionDeviceManager}.
<p>Not for use by third-party applications.
- @hide
- @SystemApi
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
android:protectionLevel="internal|role" />
+ <!-- Allows application to request to be associated with a computer to share functionality
+ and/or data with other devices, such as notifications, photos and media
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_COMPUTER})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to create a "self-managed" association.
- @hide
- @SystemApi
-->
<permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED"
android:protectionLevel="signature|privileged" />
@@ -5809,6 +5813,11 @@
<permission android:name="android.permission.MANAGE_ROTATION_RESOLVER"
android:protectionLevel="signature"/>
+ <!-- @SystemApi Allows an application to manage the cloudsearch service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to manage the music recognition service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
@@ -6081,13 +6090,13 @@
<permission android:name="android.permission.ACCESS_TV_SHARED_FILTER"
android:protectionLevel="signature|privileged|vendorPrivileged" />
- <!-- Allows an application to create trusted displays. @hide -->
+ <!-- Allows an application to create trusted displays. @hide @SystemApi -->
<permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
- <!-- Allows an application to create always-unlocked displays. @hide -->
+ <!-- Allows an application to create always-unlocked displays. @hide @SystemApi -->
<permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
- android:protectionLevel="signature"/>
+ android:protectionLevel="signature|role"/>
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
@@ -6131,6 +6140,11 @@
<permission android:name="android.permission.MANAGE_GAME_MODE"
android:protectionLevel="signature|privileged" />
+ <!-- @TestApi Allows setting the game service provider, meant for tests only.
+ @hide -->
+ <permission android:name="android.permission.SET_GAME_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows accessing the frame rate per second of a given application
@hide -->
<permission android:name="android.permission.ACCESS_FPS_COUNTER"
diff --git a/core/res/res/layout/input_method_navigation_layout.xml b/core/res/res/layout/input_method_navigation_layout.xml
index 05e750a..e0f0143 100644
--- a/core/res/res/layout/input_method_navigation_layout.xml
+++ b/core/res/res/layout/input_method_navigation_layout.xml
@@ -19,8 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginStart="@dimen/input_method_rounded_corner_content_padding"
- android:layout_marginEnd="@dimen/input_method_rounded_corner_content_padding"
+ android:layout_marginStart="@dimen/rounded_corner_content_padding"
+ android:layout_marginEnd="@dimen/rounded_corner_content_padding"
android:paddingStart="@dimen/input_method_nav_content_padding"
android:paddingEnd="@dimen/input_method_nav_content_padding"
android:clipChildren="false"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 3bc0283..408054d 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Vee uit"</string>
<string name="inputMethod" msgid="1784759500516314751">"Invoermetode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Teksaksies"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Bergingspasie word min"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sommige stelselfunksies werk moontlik nie"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nie genoeg berging vir die stelsel nie. Maak seker jy het 250 MB spasie beskikbaar en herbegin."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Laat \'n program toe om toestemming te vra om batteryoptimerings vir daardie program ignoreer."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"navraag oor alle pakkette"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Laat \'n program toe om alle geïnstalleerde pakette te sien."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"verkry toegang tot SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Gee \'n program toegang tot SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Klop twee keer vir zoembeheer"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Kon nie legstuk byvoeg nie."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Gaan"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index fb52e6a..3ac6c60 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ሰርዝ"</string>
<string name="inputMethod" msgid="1784759500516314751">"ግቤት ስልት"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"የፅሁፍ እርምጃዎች"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"የማከማቻ ቦታ እያለቀ ነው"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነጻ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"አንድ መተግበሪያ ለዚያ መተግበሪያ የባትሪ ማትባቶችን ችላ ለማለት እንዲጠይቅ ይፈቅድለታል።"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ሁሉንም ጥቅሎች ይጠይቁ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"አንድ መተግበሪያ ሁሉንም የተጫኑ ጥቅሎችን እንዲያይ ይፈቅድለታል።"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApisን ይድረሱ"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"መተግበሪያ SupplementalApisን እንዲደርስ ይፈቅዳል።"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ለአጉላ መቆጣጠሪያ ሁለት ጊዜ ነካ አድርግ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ምግብር ማከል አልተቻለም።"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ሂድ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 12d1b83..018595f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1267,6 +1267,10 @@
<string name="deleteText" msgid="4200807474529938112">"حذف"</string>
<string name="inputMethod" msgid="1784759500516314751">"طريقة الإرسال"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"إجراءات النص"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"مساحة التخزين منخفضة"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"قد لا تعمل بعض وظائف النظام"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ليست هناك مساحة تخزين كافية للنظام. تأكد من أنه لديك مساحة خالية تبلغ ٢٥٠ ميغابايت وأعد التشغيل."</string>
@@ -1571,6 +1575,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"للسماح للتطبيق بطلب الإذن لتجاهل تحسينات البطارية في هذا التطبيق."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"طلب البحث في كل الحِزم"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"يسمح هذا الإذن للتطبيق بعرض كل الحِزم المثبّتة."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"الوصول إلى SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"السماح لأحد التطبيقات بالوصول إلى SupplementalApis"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"اضغط مرتين للتحكم في التكبير أو التصغير"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"تعذرت إضافة أداة."</string>
<string name="ime_action_go" msgid="5536744546326495436">"تنفيذ"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index f57e42b..6db3b18 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"মচক"</string>
<string name="inputMethod" msgid="1784759500516314751">"ইনপুট পদ্ধতি"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"পাঠ বিষয়ক কাৰ্য"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ষ্ট’ৰেজৰ খালী ঠাই শেষ হৈ আছে"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ছিষ্টেমৰ কিছুমান কাৰ্যকলাপে কাম নকৰিবও পাৰে"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ছিষ্টেমৰ বাবে পৰ্যাপ্ত খালী ঠাই নাই। আপোনাৰ ২৫০এমবি খালী ঠাই থকাটো নিশ্চিত কৰক আৰু ৰিষ্টাৰ্ট কৰক।"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"কোনো এপক সেই এপ্টোৰ বাবে বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ অনুমতি বিচাৰিবলৈ দিয়ে।"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"আটাইবোৰ পেকেজত প্ৰশ্ন সোধক"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"এপক আটাইবোৰ ইনষ্টল কৰি থোৱা পেকেজ চাবলৈ দিয়ে।"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis এক্সেছ কৰক"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"কোনো এপ্লিকেশ্বনক SupplementalApis এক্সেছ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্ৰণ কৰিবলৈ দুবাৰ টিপক"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ৱিজেট যোগ কৰিব পৰা নগ\'ল।"</string>
<string name="ime_action_go" msgid="5536744546326495436">"যাওক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 76384f9..a6e2aab 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Sil"</string>
<string name="inputMethod" msgid="1784759500516314751">"Daxiletmə metodu"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Mətn əməliyyatları"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Yaddaş yeri bitir"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bəzi sistem funksiyaları işləməyə bilər"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistem üçün yetərincə yaddaş ehtiyatı yoxdur. 250 MB yaddaş ehtiyatının olmasına əmin olun və yenidən başladın."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Tatareya optimallaşdırılmasını o tətbiq üçün iqnor edilməsinə icazə vermək məqsədilə soruşmağa tətbiqə icazə verilir."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"bütün paketlər üçün sorğu göndərin"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Tətbiqə bütün quraşdırılmış paketləri görmək icazəsi verir."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis\'ə giriş"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Tətbiqə SupplementalApis\'ə giriş icazəsi verir."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Zoom kontrolu üçün iki dəfə toxunun"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget əlavə edilə bilmədi."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Get"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 15e3aa0..0c06851a 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Izbriši"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metod unosa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Radnje u vezi sa tekstom"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Memorijski prostor je na izmaku"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke sistemske funkcije možda ne funkcionišu"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno memorijskog prostora za sistem. Uverite se da imate 250 MB slobodnog prostora i ponovo pokrenite."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Dozvoljava aplikaciji da traži dozvolu za ignorisanje optimizacija baterije za tu aplikaciju."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"slanje upita za sve pakete"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Dozvoljava aplikaciji da vidi sve instalirane pakete."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"pristup stavci SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Dozvoljava aplikaciji da pristupa stavci SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dodirnite dvaput za kontrolu zumiranja"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nije moguće dodati vidžet."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Idi"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index e182c28..ebcc13d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Выдалiць"</string>
<string name="inputMethod" msgid="1784759500516314751">"Метад уводу"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Дзеянні з тэкстам"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Месца для захавання на зыходзе"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некаторыя сістэмныя функцыі могуць не працаваць"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Не хапае сховішча для сістэмы. Пераканайцеся, што ў вас ёсць 250 МБ свабоднага месца, і перазапусціце."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Дазваляе праграме запытваць дазвол на ігнараванне аптымізацыі акумулятара для гэтай праграмы."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"запыт усіх пакетаў"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Дазваляе праграме выяўляць усе ўсталяваныя пакеты."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"доступ да SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Дазваляе праграме мець доступ да SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Націсніце двойчы, каб кіраваць маштабаваннем"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Немагчыма дадаць віджэт."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Пачаць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 41aa9f7..8cd3ef13 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Изтриване"</string>
<string name="inputMethod" msgid="1784759500516314751">"Метод на въвеждане"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Действия с текста"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Мястото в хранилището е на изчерпване"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Възможно е някои функции на системата да не работят"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"За системата няма достатъчно място в хранилището. Уверете се, че имате свободни 250 МБ, и рестартирайте."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Разрешава на дадено приложение да иска разрешение за пренебрегване на свързаните с него оптимизации на батерията."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"заявка за всички пакети"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Разрешава на приложението да вижда всички инсталирани пакети."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"достъп до SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Позволява на приложението достъп до SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Докоснете двукратно за управление на промяната на мащаба"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Приспособлението не можа да бъде добавено."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Старт"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 7dac240d..13673f4 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"মুছুন"</string>
<string name="inputMethod" msgid="1784759500516314751">"ইনপুট পদ্ধতি"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"পাঠ্য ক্রিয়াগুলি"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"স্টোরেজ পূর্ণ হতে চলেছে"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"কিছু কিছু সিস্টেম ক্রিয়াকলাপ কাজ নাও করতে পারে"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"সিস্টেমের জন্য যথেষ্ট স্টোরেজ নেই৷ আপনার কাছে ২৫০এমবি ফাঁকা স্থান রয়েছে কিনা সে বিষয়ে নিশ্চিত হন এবং সিস্টেম চালু করুন৷"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"কোনো অ্যাপের জন্য ব্যাটারি অপ্টিমাইজেশন উপেক্ষা করতে সেটিকে অনুমতির চাওয়ার মঞ্জুরি দেয়৷"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"অন্যান্য সব প্যাকেজের তথ্য সম্পর্কিত কোয়েরি দেখুন"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"এটি কোনও অ্যাপকে সর্বদা ইনস্টল করা সব প্যাকেজ দেখতে অনুমতি দেয়।"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis অ্যাক্সেস করুন"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis অ্যাক্সেস করতে অ্যাপে অনুমতি দেয়।"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"জুম নিয়ন্ত্রণের জন্য দুবার ট্যাপ করুন"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"উইজেট যোগ করা যায়নি৷"</string>
<string name="ime_action_go" msgid="5536744546326495436">"যান"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 28ca86d..6b1afb2 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Izbriši"</string>
<string name="inputMethod" msgid="1784759500516314751">"Način unosa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Akcije za tekst"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ponestaje prostora za pohranu"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke funkcije sistema možda neće raditi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno prostora za sistem. Obezbijedite 250MB slobodnog prostora i ponovo pokrenite uređaj."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Omogućava aplikaciji da traži odobrenje za zanemarivanje optimizacije baterije za tu aplikaciju."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"upit za sve pakete"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Omogućava aplikaciji da pregleda sve instalirane pakete."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"pristup za SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Dozvoljava aplikaciji pristup za SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dodirnite dvaput za kontrolu uvećanja"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Dodavanje vidžeta nije uspjelo."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Započni"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index be1b43c..3a1baf9 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Suprimeix"</string>
<string name="inputMethod" msgid="1784759500516314751">"Mètode d\'introducció de text"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Accions de text"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"L\'espai d\'emmagatzematge s\'està esgotant"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"És possible que algunes funcions del sistema no funcionin"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hi ha prou espai d\'emmagatzematge per al sistema. Comprova que tinguis 250 MB d\'espai lliure i reinicia."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permet que una aplicació demani permís per ignorar les optimitzacions de bateria per a l\'aplicació."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar tots els paquets"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permet que una aplicació vegi els paquets instal·lats."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"accedir a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permet que una aplicació accedeixi a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Piqueu dos cops per controlar el zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"No s\'ha pogut afegir el widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ves"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index d038dfc..4936836 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Smazat"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metoda zadávání dat"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Operace s textem"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"V úložišti je málo místa"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Některé systémové funkce nemusí fungovat"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Pro systém není dostatek místa v úložišti. Uvolněte alespoň 250 MB místa a restartujte zařízení."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Povoluje aplikaci požádat o oprávnění ignorovat optimalizaci využití baterie, která pro ni je nastavena."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"zjistit všechny balíčky"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Umožňuje aplikaci načíst všechny nainstalované balíčky."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"přístup k rozhraním SupplementalApi"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Umožňuje aplikaci přístup k rozhraním SupplementalApi."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Poklepáním můžete ovládat přiblížení"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget nelze přidat."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Přejít"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index afe1175..eed13d4 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Slet"</string>
<string name="inputMethod" msgid="1784759500516314751">"Inputmetode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Teksthandlinger"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Der er snart ikke mere lagerplads"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Nogle systemfunktioner virker måske ikke"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Der er ikke nok ledig lagerplads til systemet. Sørg for, at du har 250 MB ledig plads, og genstart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Gør det muligt for en app at bede om tilladelse til at ignorere batterioptimeringer for den pågældende app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"forespørg om alle pakker"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Giver en app tilladelse til at se alle installerede pakker."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"adgang til SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Giver en app adgang til SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tryk to gange for zoomkontrol"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget kunne ikke tilføjes."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Gå"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ecaa63c..1bd20bd 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Löschen"</string>
<string name="inputMethod" msgid="1784759500516314751">"Eingabemethode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Textaktionen"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Der Speicherplatz wird knapp"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Einige Systemfunktionen funktionieren eventuell nicht."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Der Speicherplatz reicht nicht für das System aus. Stelle sicher, dass 250 MB freier Speicherplatz vorhanden sind, und starte das Gerät dann neu."</string>
@@ -1491,6 +1495,10 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Erlaubt einer App, nach der Berechtigung zum Ignorieren der Akku-Leistungsoptimierungen zu fragen."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Alle Pakete abfragen"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Ermöglicht der App, alle installierten Pakete zu sehen."</string>
+ <!-- no translation found for permlab_accessSupplementalApi (3544659160536960275) -->
+ <skip />
+ <!-- no translation found for permdesc_accessSupplementalApi (8974758769370951074) -->
+ <skip />
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Für Zoomeinstellung zweimal berühren"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget konnte nicht hinzugefügt werden."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Los"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 44fe426..52ab901 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Διαγραφή"</string>
<string name="inputMethod" msgid="1784759500516314751">"Μέθοδος εισόδου"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Ενέργειες κειμένου"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ο αποθηκευτικός χώρος εξαντλείται"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Ορισμένες λειτουργίες συστήματος ενδέχεται να μην λειτουργούν"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Δεν υπάρχει αρκετός αποθηκευτικός χώρος για το σύστημα. Βεβαιωθείτε ότι διαθέτετε 250 MB ελεύθερου χώρου και κάντε επανεκκίνηση."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Επιτρέπει σε μια εφαρμογή να ζητήσει άδεια για την αγνόηση βελτιστοποιήσεων της μπαταρίας για τη συγκεκριμένη εφαρμογή."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"υποβολή ερωτήματος σε όλα τα πακέτα"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Επιτρέπει σε μια εφαρμογή να βλέπει όλα τα εγκατεστημένα πακέτα."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"πρόσβαση στο SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Επιτρέπει σε μια εφαρμογή να έχει πρόσβαση στο SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Πατήστε δύο φορές για έλεγχο εστίασης"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Δεν ήταν δυνατή η προσθήκη του γραφικού στοιχείου."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Μετάβαση"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a23eda2..9a40fa6 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Input method"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Text actions"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Allows an application to access SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Couldn\'t add widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Go"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 281ecb1..1a2e8d9 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Input method"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Text actions"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Allows an application to access SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Couldn\'t add widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Go"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 8d8b33c..3039233 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Input method"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Text actions"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Allows an application to access SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Couldn\'t add widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Go"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index e411655..0e93f31 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Input method"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Text actions"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Allows an application to access SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Couldn\'t add widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Go"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index c2e7472..db0f559 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1187,6 +1187,8 @@
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Input method"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Text actions"</string>
+ <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Back"</string>
+ <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
@@ -1491,6 +1493,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimizations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Allows an application to access SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Couldn\'t add widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Go"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index fad71eb7..dc8f357 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Eliminar"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de entrada"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Acciones de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio de almacenamiento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Es posible que algunas funciones del sistema no estén disponibles."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hay espacio suficiente para el sistema. Asegúrate de que haya 250 MB libres y reinicia el dispositivo."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite que una app solicite permiso para ignorar las optimizaciones de la batería."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Búsqueda de todos los paquetes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite que una app vea todos los paquetes que se instalaron."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"Acceder a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite que una aplicación acceda a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Presiona dos veces para obtener el control del zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"No se pudo agregar el widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 669e914..ec17d22 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Eliminar"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de introducción de texto"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Acciones de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Queda poco espacio"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Es posible que algunas funciones del sistema no funcionen."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"No hay espacio suficiente para el sistema. Comprueba que haya 250 MB libres y reinicia el dispositivo."</string>
@@ -1264,7 +1268,7 @@
<string name="screen_compat_mode_hint" msgid="4032272159093750908">"Para volver a habilitar esta opción, accede a Ajustes > Aplicaciones > Descargadas."</string>
<string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite el tamaño de pantalla actual y es posible que funcione de forma inesperada."</string>
<string name="unsupported_display_size_show" msgid="980129850974919375">"Mostrar siempre"</string>
- <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> se diseñó para una versión incompatible de Android OS y puede que funcione de forma inesperada. Es posible que haya una versión actualizada de la aplicación."</string>
+ <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> se diseñó para una versión incompatible del SO Android y puede que funcione de forma inesperada. Es posible que haya una versión actualizada de la aplicación."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Mostrar siempre"</string>
<string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Buscar actualizaciones"</string>
<string name="smv_application" msgid="3775183542777792638">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha infringido su política StrictMode autoaplicable."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite que una aplicación solicite permiso para ignorar las optimizaciones de la batería."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar todos los paquetes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite que una aplicación vea todos los paquetes instalados."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"acceder a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite que una aplicación acceda a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Da dos toques para acceder al control de zoom."</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"No se ha podido añadir el widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index e4d33a7..30e78c1 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Kustuta"</string>
<string name="inputMethod" msgid="1784759500516314751">"Sisestusmeetod"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tekstitoimingud"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Talletusruum saab täis"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Mõned süsteemifunktsioonid ei pruugi töötada"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Süsteemis pole piisavalt talletusruumi. Veenduge, et seadmes oleks 250 MB vaba ruumi, ja käivitage seade uuesti."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Lubab rakendusel küsida luba rakenduse aku optimeerimise eiramiseks."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"päringute esitamine kõikide pakettide kohta"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Võimaldab rakendusel näha kõiki installitud pakette."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"juurdepääs üksusele SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Annab rakendusele juurdepääsu üksusele SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Suumi kasutamiseks koputage kaks korda"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Vidinat ei saanud lisada."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Mine"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index a3766b76..1c301e6 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Ezabatu"</string>
<string name="inputMethod" msgid="1784759500516314751">"Idazketa-metodoa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Testu-ekintzak"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Memoria betetzen ari da"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sistemaren funtzio batzuek ez dute agian funtzionatuko"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sisteman ez dago behar adina memoria. Ziurtatu gutxienez 250 MB erabilgarri dituzula eta, ondoren, berrabiarazi gailua."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Bateriaren optimizazioei ez ikusi egiteko baimena eskatzea baimentzen die aplikazioei."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Kontsultatu pakete guztiak"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Instalatutako pakete guztiak ikusteko baimena ematen dio aplikazioari."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"atzitu SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis direlakoak (API osagarriak) atzitzeko baimena ematen die aplikazioei."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Sakatu birritan zooma kontrolatzeko"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Ezin izan da widgeta gehitu."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Joan"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index c9dedf9..ca06cf9 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"حذف"</string>
<string name="inputMethod" msgid="1784759500516314751">"روش ورودی"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"کنشهای متنی"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"فضای ذخیرهسازی درحال پر شدن است"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"برخی از عملکردهای سیستم ممکن است کار نکنند"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"فضای ذخیرهسازی سیستم کافی نیست. اطمینان حاصل کنید که دارای ۲۵۰ مگابایت فضای خالی هستید و سیستم را راهاندازی مجدد کنید."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"به یک برنامه اجازه میدهد جهت نادیده گرفتن بهینهسازی باتری برای خود مجوز درخواست کند."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"پُرسمان همه بستهها"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"به برنامه اجازه میدهد همه بستههای نصبشده را ببیند."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"دسترسی به SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"به برنامه اجازه میدهد به SupplementalApis دسترسی داشته باشد."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"برای کنترل بزرگنمایی، دو بار ضربه بزنید"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"افزودن ابزارک انجام نشد."</string>
<string name="ime_action_go" msgid="5536744546326495436">"برو"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index b7234be..6bac5ac 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Poista"</string>
<string name="inputMethod" msgid="1784759500516314751">"Syöttötapa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tekstitoiminnot"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Tallennustila loppumassa"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Kaikki järjestelmätoiminnot eivät välttämättä toimi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tallennustila ei riitä. Varmista, että vapaata tilaa on 250 Mt, ja käynnistä uudelleen."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Sallii sovelluksen pyytää lupaa ohittaa tietyn sovelluksen akun optimoinnit."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"kaikkien pakettien näkeminen"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Antaa sovelluksen nähdä kaikki asennetut paketit."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"pääsy SupplementalApi-rajapintoihin"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Sovellus saa pääsyn SupplementalApi-rajapintoihin."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Hallitse zoomausta napauttamalla kahdesti"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widgetin lisääminen epäonnistui."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Siirry"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 6a08237..2db9e97 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Supprimer"</string>
<string name="inputMethod" msgid="1784759500516314751">"Mode de saisie"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Actions sur le texte"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Espace de stockage bientôt saturé"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permet à une application de demander la permission d\'ignorer les optimisations de la pile."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"envoyer une requête à propos de tous les paquets"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permet à une application de voir tous les paquets installés."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"accès à SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Autorise une application à accéder à SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Appuyer deux fois pour régler le zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Impossible d\'ajouter le widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Aller"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c606ed3..bb28b02 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Supprimer"</string>
<string name="inputMethod" msgid="1784759500516314751">"Mode de saisie"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Actions sur le texte"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Espace de stockage bientôt saturé"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Autorise une application à demander l\'autorisation d\'ignorer les optimisations de batterie pour cette application."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"interroger tous les packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Autorise une appli à voir tous les packages installés."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"accéder à SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Autorise une application à accéder à SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Appuyer deux fois pour régler le zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Impossible d\'ajouter le widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"OK"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 24287d9..47247bf 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Eliminar"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de introdución de texto"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Accións de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Estase esgotando o espazo de almacenamento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"É posible que algunhas funcións do sistema non funcionen"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Non hai almacenamento suficiente para o sistema. Asegúrate de ter un espazo libre de 250 MB e reinicia o dispositivo."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Fai que unha aplicación poida solicitar permiso para ignorar as optimizacións da batería."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar todos os paquetes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite que unha aplicación consulte todos os paquetes instalados."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"acceso a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite que unha aplicación acceda a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Toca dúas veces para controlar o zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Non se puido engadir o widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 5ca2c39..b0efeab 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ડિલીટ કરો"</string>
<string name="inputMethod" msgid="1784759500516314751">"ઇનપુટ પદ્ધતિ"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ટેક્સ્ટ ક્રિયાઓ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"સ્ટોરેજ સ્થાન સમાપ્ત થયું"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"કેટલાક સિસ્ટમ Tasks કામ કરી શકશે નહીં"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"સિસ્ટમ માટે પર્યાપ્ત સ્ટોરેજ નથી. ખાતરી કરો કે તમારી પાસે 250MB ખાલી સ્થાન છે અને ફરીથી પ્રારંભ કરો."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ઍપ્લિકેશનને તે ઍપ્લિકેશન માટે બૅટરી ઓપ્ટિમાઇઝેશન્સને અવગણવાની પરવાનગી આપવા માટે પૂછવાની મંજૂરી આપે છે."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"બધા પૅકેજ જુઓ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"કોઈ ઍપને ઇન્સ્ટૉલ કરેલા બધા પૅકેજ જોવાની મંજૂરી આપે છે."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApisને ઍક્સેસ કરો"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"કોઈ ઍપ્લિકેશનને SupplementalApis ઍક્સેસ કરવાની મંજૂરી આપે છે."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ઝૂમ નિયંત્રણ માટે બેવાર ટૅપ કરો"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"વિજેટ ઉમેરી શકાયું નથી."</string>
<string name="ime_action_go" msgid="5536744546326495436">"જાઓ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index b846ff8..70d0270 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"मिटाएं"</string>
<string name="inputMethod" msgid="1784759500516314751">"इनपुट विधि"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"लेख क्रियाएं"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"मेमोरी में जगह नहीं बची है"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"हो सकता है कुछ सिस्टम फ़ंक्शन काम नहीं करें"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टम के लिए ज़रूरी मेमोरी नहीं है. पक्का करें कि आपके पास 250एमबी की खाली जगह है और फिर से शुरू करें."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"किसी ऐप्लिकेशन को उस ऐप्लिकेशन के लिए बैटरी ऑप्टिमाइज़ेशन पर ध्यान ना देने की अनुमति के लिए पूछने देता है."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"डिवाइस पर इंस्टॉल किए गए सभी पैकेज देखें"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"यह किसी ऐप्लिकेशन को, डिवाइस पर इंस्टॉल किए गए सभी पैकेज देखने की अनुमति देता है."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis का ऐक्सेस पाना"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"इससे, किसी ऐप्लिकेशन को SupplementalApis को ऐक्सेस करने की अनुमति मिलती है."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ज़ूम नियंत्रण के लिए दो बार टैप करें"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"विजेट नहीं जोड़ा जा सका."</string>
<string name="ime_action_go" msgid="5536744546326495436">"जाएं"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e88b331..d48713f 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Izbriši"</string>
<string name="inputMethod" msgid="1784759500516314751">"Način unosa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Radnje s tekstom"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ponestaje prostora za pohranu"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke sistemske funkcije možda neće raditi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno pohrane za sustav. Oslobodite 250 MB prostora i pokrenite uređaj ponovo."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Aplikaciji omogućuje da traži dopuštenje za zanemarivanje optimizacija baterije za tu aplikaciju."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"slanje upita za sve pakete"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Aplikaciji omogućuje pregled instaliranih paketa."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"pristupiti SupplementalApijima"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Omogućuje aplikaciji pristupanje SupplementalApijima."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dvaput dotaknite za upravljanje zumiranjem"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget nije moguće dodati."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Idi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 46fcc2a..5d3fa9f 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Törlés"</string>
<string name="inputMethod" msgid="1784759500516314751">"Beviteli mód"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Műveletek szöveggel"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Kevés a szabad terület"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Előfordulhat, hogy néhány rendszerfunkció nem működik."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nincs elegendő tárhely a rendszerhez. Győződjön meg arról, hogy rendelkezik 250 MB szabad területtel, majd kezdje elölről."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Az alkalmazás engedélyt kérhet az akkumulátoroptimalizálási beállítások mellőzésére."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"az összes csomag lekérdezése"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Engedélyezi az adott alkalmazás számára, hogy lássa az összes telepített csomagot."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"hozzáférés ehhez: SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Lehetővé teszi egy alkalmazás számára, hogy hozzáférjen a következőhöz: SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Érintse meg kétszer a nagyítás beállításához"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nem sikerült hozzáadni a modult."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ugrás"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index b60f08b..8b6ef09 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Ջնջել"</string>
<string name="inputMethod" msgid="1784759500516314751">"Մուտքագրման եղանակը"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Տեքստի գործողությունները"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Հիշողությունը սպառվում է"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Որոշ գործառույթներ կարող են չաշխատել"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Համակարգի համար բավարար հիշողություն չկա: Համոզվեք, որ ունեք 250ՄԲ ազատ տարածություն և վերագործարկեք:"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Հավելվածին հնարավորություն է տալիս հայցելու թույլտվություն՝ տվյալ հավելվածի համար մարտկոցի օպտիմալացումն անտեսելու համար:"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"հարցում բոլոր փաթեթների համար"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Թույլ է տալիս հավելվածին տեսնել բոլոր տեղադրված փաթեթները։"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"օգտվել SupplementalApis-ից"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Թույլ է տալիս հավելվածին օգտվել SupplementalApis-ից։"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Հպեք երկու անգամ` խոշորացման վերահսկման համար"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Չհաջողվեց վիջեթ ավելացնել:"</string>
<string name="ime_action_go" msgid="5536744546326495436">"Առաջ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 7925b3f..10bfa5e 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Hapus"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metode masukan"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tindakan teks"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ruang penyimpanan hampir habis"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Beberapa fungsi sistem mungkin tidak dapat bekerja"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Penyimpanan tidak cukup untuk sistem. Pastikan Anda memiliki 250 MB ruang kosong, lalu mulai ulang."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Mengizinkan aplikasi meminta izin untuk mengabaikan pengoptimalan baterai bagi aplikasi tersebut."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"mengkueri semua paket"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Mengizinkan aplikasi melihat semua paket yang diinstal."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"akses SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Mengizinkan aplikasi mengakses SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Ketuk dua kali untuk kontrol perbesar/perkecil"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Tidak dapat menambahkan widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Buka"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index c758de5..79c2c68 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Eyða"</string>
<string name="inputMethod" msgid="1784759500516314751">"Innsláttaraðferð"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Textaaðgerðir"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Geymslurýmið er senn á þrotum"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Sumir kerfiseiginleikar kunna að vera óvirkir"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Ekki nægt geymslurými fyrir kerfið. Gakktu úr skugga um að 250 MB séu laus og endurræstu."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Gerir forriti kleift að biðja um heimild til að hunsa rafhlöðusparnað fyrir forritið."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"spyrja fyrir alla pakka"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Leyfir forriti að sjá alla uppsetta pakka."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"aðgangur að SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Veitir forriti aðgang að SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Ýttu tvisvar til að opna aðdráttarstýringar"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Ekki tókst að bæta græju við."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Áfram"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index c5683fa..a27a092 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Elimina"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metodo inserimento"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Azioni testo"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Spazio di archiviazione in esaurimento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Alcune funzioni di sistema potrebbero non funzionare"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Memoria insufficiente per il sistema. Assicurati di avere 250 MB di spazio libero e riavvia."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Consente a un\'app di chiedere l\'autorizzazione a ignorare le ottimizzazioni della batteria per quell\'app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Invio di query per tutti i pacchetti"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Consente a un\'app di visualizzare tutti i pacchetti installati."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"Accesso a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Consente a un\'applicazione di accedere a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tocca due volte per il comando dello zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Aggiunta del widget non riuscita."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Vai"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 9da03ea..c234ea1 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"מחיקה"</string>
<string name="inputMethod" msgid="1784759500516314751">"שיטת קלט"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"פעולות טקסט"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"מקום האחסון עומד להיגמר"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ייתכן שפונקציות מערכת מסוימות לא יפעלו"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"אין מספיק מקום אחסון עבור המערכת. עליך לוודא שיש לך מקום פנוי בנפח של 250MB ולהתחיל שוב."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"מאפשרת לאפליקציה לבקש רשות להתעלם מאופטימיזציות של הסוללה לאפליקציה הזו."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"שליחת שאילתות לכל החבילות"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"מאפשרת לאפליקציה לראות את כל החבילות המותקנות."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"גישה אל SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"מאפשרת לאפליקציה לגשת אל SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"יש להקיש פעמיים לשינוי המרחק מהתצוגה"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"לא ניתן להוסיף widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"התחלה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 76d248c..736fca6 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"削除"</string>
<string name="inputMethod" msgid="1784759500516314751">"入力方法"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"テキスト操作"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"空き容量わずか"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"一部のシステム機能が動作しない可能性があります"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"システムに十分な容量がありません。250MBの空き容量を確保して再起動してください。"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"電池の最適化の無視についてアプリが確認することを許可します。"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"すべてのパッケージを照会する"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"すべてのインストール済みパッケージを参照することをアプリに許可します。"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis へのアクセス"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis へのアクセスをアプリに許可します。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ダブルタップでズームします"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ウィジェットを追加できませんでした。"</string>
<string name="ime_action_go" msgid="5536744546326495436">"移動"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 91144c6..e0b14a9 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"წაშლა"</string>
<string name="inputMethod" msgid="1784759500516314751">"შეყვანის მეთოდი"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ქმედებები ტექსტზე"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"თავისუფალი ადგილი იწურება"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"სისტემის ზოგიერთმა ფუნქციამ შესაძლოა არ იმუშავოს"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"სისტემისათვის საკმარისი საცავი არ არის. დარწმუნდით, რომ იქონიოთ სულ მცირე 250 მბაიტი თავისუფალი სივრცე და დაიწყეთ ხელახლა."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"საშუალებას მისცემს აპს, მოითხოვოს მასთან დაკავშირებული ბატარეის ოპტიმიზაციის იგნორირების ნებართვა."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ყველა პაკეტის მოთხოვნა"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"საშუალებას აძლევს აპს, ნახოს ყველა ინსტალირებული პაკეტი."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis-ზე წვდომა"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"აპლიკაციას SupplementalApis-ზე წვდომის საშუალებას აძლევს."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"მასშტაბის ცვლილებისთვის შეეხეთ ორჯერ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ვერ დაემატა ვიჯეტი."</string>
<string name="ime_action_go" msgid="5536744546326495436">"გადასვლა"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 3f75495..3a130bd 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Жою"</string>
<string name="inputMethod" msgid="1784759500516314751">"Енгізу әдісі"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Мәтін әрекеттері"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Жадта орын азайып барады"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Жүйенің кейбір функциялары жұмыс істемеуі мүмкін"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Жүйе үшін жад жеткіліксіз. 250 МБ бос орын бар екенін тексеріп, қайта іске қосыңыз."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Қолданба батареяны оңтайландыру әрекетін елемеуді сұрай алады."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"барлық бумаға сұрау жасау"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Қолданба барлық орнатылған буманы көре алады."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApi интерфейстерін пайдалану"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Қолданбаға SupplementalApi интерфейстерін пайдалануға мүмкіндік береді."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Масштабтау параметрін басқару үшін екі рет түртіңіз"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Виджетті қосу."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Өту"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index ecae41a..bb25bae0 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"លុប"</string>
<string name="inputMethod" msgid="1784759500516314751">"វិធីសាស្ត្របញ្ចូល"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"សកម្មភាពអត្ថបទ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"អស់ទំហំផ្ទុក"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"មុខងារប្រព័ន្ធមួយចំនួនអាចមិនដំណើរការ"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"មិនមានទំហំផ្ទុកគ្រប់គ្រាន់សម្រាប់ប្រព័ន្ធ។ សូមប្រាកដថាអ្នកមានទំហំទំនេរ 250MB ហើយចាប់ផ្ដើមឡើងវិញ។"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"អនុញ្ញាតឲ្យកម្មវិធីស្នើសុំការអនុញ្ញាត ដើម្បីមិនអើពើចំពោះការបង្កើនប្រសិទ្ធភាពថ្ម។"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"សួរសំណួរអំពីកញ្ចប់ទាំងអស់"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"អនុញ្ញាតឱ្យកម្មវិធីមើលកញ្ចប់ដែលបានដំឡើងទាំងអស់។"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"ចូលប្រើ SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"អនុញ្ញាតឱ្យកម្មវិធីចូលប្រើ SupplementalApis។"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ប៉ះ ពីរដងដើម្បីពិនិត្យការពង្រីក"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"មិនអាចបន្ថែមធាតុក្រាហ្វិក។"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ទៅ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index a24166c..06075ac 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ಅಳಿಸಿ"</string>
<string name="inputMethod" msgid="1784759500516314751">"ಇನ್ಪುಟ್ ವಿಧಾನ"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ಪಠ್ಯದ ಕ್ರಮಗಳು"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ಸಂಗ್ರಹಣೆ ಸ್ಥಳವು ತುಂಬಿದೆ"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ಕೆಲವು ಸಿಸ್ಟಂ ಕಾರ್ಯವಿಧಾನಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ಸಿಸ್ಟಂನಲ್ಲಿ ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆಯಿಲ್ಲ. ನೀವು 250MB ನಷ್ಟು ಖಾಲಿ ಸ್ಥಳವನ್ನು ಹೊಂದಿರುವಿರಾ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಹಾಗೂ ಮರುಪ್ರಾರಂಭಿಸಿ."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ಈ ಅಪ್ಲಿಕೇಶನ್ಗೆ ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್ಗಳನ್ನು ಕಡೆಗಣಿಸುವುದಕ್ಕೆ ಅನುಮತಿಯನ್ನು ಕೇಳಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ಎಲ್ಲಾ ಪ್ಯಾಕೇಜ್ಗಳ ಕುರಿತಾದ ಮಾಹಿತಿಯನ್ನು ಕೇಳಿ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ ಎಲ್ಲಾ ಪ್ಯಾಕೇಜ್ಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis ಅನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ಝೂಮ್ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ವಿಜೆಟ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ."</string>
<string name="ime_action_go" msgid="5536744546326495436">"ಹೋಗು"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 8920be2..bbfae68 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"삭제"</string>
<string name="inputMethod" msgid="1784759500516314751">"입력 방법"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"텍스트 작업"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"저장 공간이 부족함"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"일부 시스템 기능이 작동하지 않을 수 있습니다."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"시스템의 저장 공간이 부족합니다. 250MB의 여유 공간이 확보한 후 다시 시작하세요."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"앱에서 배터리 최적화를 무시할 수 있는 권한을 요청할 수 있도록 허용합니다."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"모든 패키지 쿼리"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"앱이 설치된 패키지를 모두 볼 수 있도록 허용합니다."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis 액세스"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"애플리케이션에서 SupplementalApis에 액세스하도록 허용합니다."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"확대/축소하려면 두 번 탭하세요."</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"위젯을 추가할 수 없습니다."</string>
<string name="ime_action_go" msgid="5536744546326495436">"이동"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 50c9393..aa7f5aa 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Жок кылуу"</string>
<string name="inputMethod" msgid="1784759500516314751">"Киргизүү ыкмасы"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Текст боюнча иштер"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сактагычта орун калбай баратат"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Айрым функциялар иштебеши мүмкүн"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Тутумда сактагыч жетишсиз. 250МБ бош орун бар экенин текшерип туруп, өчүрүп күйгүзүңүз."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Колдонмо батареянын кубатын керектегенден мурун уруксат суралсын."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"бардык топтомдор боюнча сурам жөнөтүү"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Колдонмо бардык орнотулган топтомдорду көрөт."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis\'ке кирүү"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Колдонмого SupplementalApis\'ке кирүү уруксатын берет."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Масштабдын параметрлерин өзгөртүү үчүн бул жерди эки жолу басыңыз."</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Виджетти кошуу мүмкүн болбоду."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Өтүү"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 398b6b3..fc68211 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ລຶບ"</string>
<string name="inputMethod" msgid="1784759500516314751">"ຮູບແບບການປ້ອນຂໍ້ມູນ"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ການເຮັດວຽກຂອງຂໍ້ຄວາມ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນກຳລັງຈະເຕັມ"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ການເຮັດວຽກບາງຢ່າງຂອງລະບົບບາງອາດຈະໃຊ້ບໍ່ໄດ້"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ບໍ່ມີບ່ອນເກັບຂໍ້ມູນພຽງພໍສຳລັບລະບົບ. ກວດສອບໃຫ້ແນ່ໃຈວ່າທ່ານມີພື້ນທີ່ຫວ່າງຢ່າງໜ້ອຍ 250MB ແລ້ວລອງໃໝ່."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ອະນຸຍາດໃຫ້ແອັບຖາມສິດອະນຸຍາດເພື່ອເພີກເສີຍຕໍ່ການປັບແຕ່ງແບັດເຕີຣີສຳລັບແອັບນັ້ນ."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ຊອກຫາແພັກເກດທັງໝົດ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ອະນຸຍາດໃຫ້ແອັບເບິ່ງເຫັນແພັກເກດທີ່ຕິດຕັ້ງແລ້ວທັງໝົດໄດ້."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"ເຂົ້າເຖິງ SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນເຂົ້າເຖິງ SupplementalApis ໄດ້."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ແຕະສອງເທື່ອເພື່ອຄວບຄຸມການຊູມ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ບໍ່ສາມາດເພີ່ມວິດເຈັດໄດ້."</string>
<string name="ime_action_go" msgid="5536744546326495436">"ໄປ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index de36cb6..ef7b0f1 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Ištrinti"</string>
<string name="inputMethod" msgid="1784759500516314751">"Įvesties būdas"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Teksto veiksmai"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Mažėja laisvos saugyklos vietos"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Kai kurios sistemos funkcijos gali neveikti"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistemos saugykloje nepakanka vietos. Įsitikinkite, kad yra 250 MB laisvos vietos, ir paleiskite iš naujo."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Programai leidžiama prašyti leidimo nepaisyti tai programai skirto akumuliatoriaus optimizavimo nustatymų."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Teikti visų paketų užklausą"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Programai leidžiama peržiūrėti visus įdiegtus paketus."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"pasiekti papildomas API"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Leidžiama programai pasiekti papildomas API."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Bakstelėkite du kartus, kad valdytumėte mastelio keitimą"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nepavyko pridėti."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Pradėti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 93414e0..68725cb 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -982,7 +982,7 @@
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Logrīks <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> ir izdzēsts."</string>
<string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"Izvērst atbloķēšanas apgabalu."</string>
<string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"Autorizācija, velkot ar pirkstu."</string>
- <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Autorizācija ar kombināciju."</string>
+ <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Atbloķēšanas kombinācija."</string>
<string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Autorizācija pēc sejas."</string>
<string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Autorizācija ar PIN kodu."</string>
<string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM kartes atbloķēšanas PIN"</string>
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Dzēst"</string>
<string name="inputMethod" msgid="1784759500516314751">"Ievades metode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Teksta darbības"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Paliek maz brīvas vietas"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Dažas sistēmas funkcijas var nedarboties."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistēmai pietrūkst vietas. Atbrīvojiet vismaz 250 MB vietas un restartējiet ierīci."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Ļauj lietotnei lūgt atļauju ignorēt akumulatora optimizāciju šai lietotnei."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"pieprasīt atļauju skatīt visas pakotnes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Ļauj lietotnei skatīt visas instalētās pakotnes."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"piekļuve SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Ļauj lietojumprogrammai piekļūt SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Pieskarieties divreiz, lai kontrolētu tālummaiņu."</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nevarēja pievienot logrīku."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Doties uz"</string>
@@ -1917,7 +1923,7 @@
<string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2. darba profils: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3. darba profils: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Prasīt PIN kodu pirms atspraušanas"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pirms atspraušanas pieprasīt grafisko atsl."</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pirms atspraušanas pieprasīt atbloķēšanas kombināciju"</string>
<string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pirms atspraušanas pieprasīt paroli"</string>
<string name="package_installed_device_owner" msgid="7035926868974878525">"Instalēja administrators"</string>
<string name="package_updated_device_owner" msgid="7560272363805506941">"Atjaunināja administrators"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index d4f099f..1ccfc7e 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Избриши"</string>
<string name="inputMethod" msgid="1784759500516314751">"Метод на внес"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Дејства со текст"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Капацитетот е речиси полн"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некои системски функции може да не работат"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Нема доволно меморија во системот. Проверете дали има слободен простор од 250 MB и рестартирајте."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Овозможува апликацијата да побара дозвола за игнорирање на оптимизациите на батеријата за таа апликација."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"пребарување на сите пакети"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Дозволува апликацијата да ги гледа сите инсталирани пакети."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"пристап до SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Дозволува апликацијата да пристапува до SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Допрете двапати за контрола на зумот"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Не може да се додаде виџет."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Оди"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1c98f25..77540e1 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ഇല്ലാതാക്കുക"</string>
<string name="inputMethod" msgid="1784759500516314751">"ടൈപ്പുചെയ്യൽ രീതി"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ടെക്സ്റ്റ് പ്രവർത്തനങ്ങൾ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"സംഭരണയിടം കഴിഞ്ഞു"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ചില സിസ്റ്റം പ്രവർത്തനങ്ങൾ പ്രവർത്തിക്കണമെന്നില്ല."</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"സിസ്റ്റത്തിനായി മതിയായ സംഭരണമില്ല. 250MB സൗജന്യ സംഭരണമുണ്ടെന്ന് ഉറപ്പുവരുത്തി പുനരാരംഭിക്കുക."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ആപ്പിന് വേണ്ടിയുള്ള ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകളെ അവഗണിക്കാനുള്ള അനുമതി ചോദിക്കുന്നതിന് ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"എല്ലാ പാക്കേജുകളും നോക്കുക"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ഇൻസ്റ്റാൾ ചെയ്ത എല്ലാ പാക്കേജുകളും കാണാൻ ഒരു ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis ആക്സസ് ചെയ്യുക"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis ആക്സസ് ചെയ്യാൻ ഒരു ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"സൂം നിയന്ത്രണം ലഭിക്കാൻ രണ്ടുതവണ ടാപ്പുചെയ്യുക"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"വിജറ്റ് ചേർക്കാനായില്ല."</string>
<string name="ime_action_go" msgid="5536744546326495436">"പോവുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index db67e90..dcb4859 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Устгах"</string>
<string name="inputMethod" msgid="1784759500516314751">"Оруулах арга"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Текст үйлдэл"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Сангийн хэмжээ дутагдаж байна"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Зарим систем функц ажиллахгүй байна"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Системд хангалттай сан байхгүй байна. 250MБ чөлөөтэй зай байгаа эсэхийг шалгаад дахин эхлүүлнэ үү."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Тухайн аппaaс батерейны оновчлол алгасах зөвшөөрөл асуухыг зөвшөөрдөг."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"бүх багцыг лавлах"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Аппап бүх суулгасан багцыг харахыг зөвшөөрнө."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis-д хандах"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Аппликэйшнд SupplementalApis-д хандах зөвшөөрлийг олгодог."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Өсгөх контрол дээр хоёр удаа товшино уу"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Виджет нэмж чадсангүй."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Очих"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 1385dcd..9cac6c8 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"हटवा"</string>
<string name="inputMethod" msgid="1784759500516314751">"इनपुट पद्धत"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"मजकूर क्रिया"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"संचयन स्थान संपत आहे"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"काही सिस्टम कार्ये कार्य करू शकत नाहीत"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टीमसाठी पुरेसे संचयन नाही. आपल्याकडे 250MB मोकळे स्थान असल्याचे सुनिश्चित करा आणि रीस्टार्ट करा."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"त्या ॲपसाठी बॅटरी ऑप्टिमायझेशन दुर्लक्षित करण्यासाठी ॲपला परवानगी मागण्याची अनुमती देते."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"सर्व पॅकेजविषयी क्वेरी करा"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ॲपला इंस्टॉल केलेले सर्व पॅकेज पाहण्याची अनुमती देते."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis अॅक्सेस करा"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"अॅप्लिकेशनला SupplementalApis अॅक्सेस करण्याची अनुमती देते."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"झूम नियंत्रणासाठी दोनदा टॅप करा"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"विजेट जोडू शकलो नाही."</string>
<string name="ime_action_go" msgid="5536744546326495436">"जा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 43847fe..1f5b818 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Padam"</string>
<string name="inputMethod" msgid="1784759500516314751">"Kaedah input"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tindakan teks"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Ruang storan semakin berkurangan"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Beberapa fungsi sistem mungkin tidak berfungsi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tidak cukup storan untuk sistem. Pastikan anda mempunyai 250MB ruang kosong dan mulakan semula."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Membenarkan apl meminta kebenaran untuk mengabaikan pengoptimuman bateri untuk apl itu."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"buat pertanyaan untuk semua pakej"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Membenarkan apl melihat semua pakej yang dipasang."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"akses SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Membenarkan aplikasi mengakses SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Ketik dua kali untuk mendapatkan kawalan zum"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Tidak dapat menambahkan widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Pergi"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index f509906..b244af5 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ဖျက်ရန်"</string>
<string name="inputMethod" msgid="1784759500516314751">"ထည့်သွင်းရန်နည်းလမ်း"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"စာတို လုပ်ဆောင်ချက်"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"သိမ်းဆည်သော နေရာ နည်းနေပါသည်"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"တချို့ စနစ်လုပ်ငန်းများ အလုပ် မလုပ်ခြင်း ဖြစ်နိုင်ပါသည်"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"စနစ်အတွက် သိုလှောင်ခန်း မလုံလောက်ပါ။ သင့်ဆီမှာ နေရာလွတ် ၂၅၀ MB ရှိတာ စစ်ကြည့်ပြီး စတင်ပါ။"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ဘက်ထရီ ပိုမိုကောင်းမွန်အောင် ပြုလုပ်ခြင်းကို လျစ်လျူရှုရန်အတွက် ခွင့်ပြုချက်တောင်းရန် အက်ပ်ကို ခွင့်ပြုပါ။"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ပက်ကေ့ဂျ်အားလုံးကို မေးမြန်းခြင်း"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ထည့်သွင်းထားသော ပက်ကေ့ဂျ်အားလုံး ကြည့်ရန် အက်ပ်ကို ခွင့်ပြုပါ။"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis သုံးခွင့်"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"အပလီကေးရှင်းအား SupplementalApis သုံးခွင့်ပေးနိုင်သည်။"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ဇူးမ်အသုံးပြုရန် နှစ်ချက်တို့ပါ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ဝဒ်ဂျက်ထည့်လို့ မရပါ"</string>
<string name="ime_action_go" msgid="5536744546326495436">"သွားပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 2ddd45d..a792d37 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Slett"</string>
<string name="inputMethod" msgid="1784759500516314751">"Inndatametode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Teksthandlinger"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lite ledig lagringsplass"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontrollér at du har 250 MB ledig plass, og start på nytt."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Gjør det mulig for apper å be om tillatelse til å ignorere batterioptimaliseringer for disse appene."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"søk i alle pakker"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Lar en app se alle installerte pakker."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"tilgang til SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Gir appen tilgang til SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Trykk to ganger for zoomkontroll"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Kunne ikke legge til modulen."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Utfør"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index d4828b6..fe9c52b 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"मेट्नुहोस्"</string>
<string name="inputMethod" msgid="1784759500516314751">"निवेश विधि"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"पाठ कार्यहरू"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"भण्डारण ठाउँ सकिँदै छ"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"सायद केही प्रणाली कार्यक्रमहरूले काम गर्दैनन्"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"प्रणालीको लागि पर्याप्त भण्डारण छैन। तपाईँसँग २५० मेगा बाइट ठाउँ खाली भएको निश्चित गर्नुहोस् र फेरि सुरु गर्नुहोस्।"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"कुनै एपलाई त्यसका ब्याट्री सम्बन्धी अनुकूलनहरूलाई बेवास्ता गर्नाका लागि अनुमति माग्न दिन्छ।"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"सबै प्याकेजहरू खोज्नुहोस्"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"यसले यस एपलाई इन्स्टल गरिएका सबै प्याकेजहरू हेर्ने अनुमति दिन्छ।"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis प्रयोग गर्ने अनुमति"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"एपलाई SupplementalApis प्रयोग गर्ने अनुमति दिन्छ।"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"जुम नियन्त्रणको लागि दुई चोटि ट्याप गर्नुहोस्"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"विजेट थप गर्न सकिँदैन।"</string>
<string name="ime_action_go" msgid="5536744546326495436">"जानुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 3b6db06..686c951 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Verwijderen"</string>
<string name="inputMethod" msgid="1784759500516314751">"Invoermethode"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tekstacties"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Opslagruimte is bijna vol"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bepaalde systeemfuncties werken mogelijk niet"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Onvoldoende opslagruimte voor het systeem. Zorg ervoor dat je 250 MB vrije ruimte hebt en start opnieuw."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Hiermee kan een app rechten vragen om batterijoptimalisatie voor die app te negeren."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"alle pakketten opvragen"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Hiermee kan een app alle geïnstalleerde pakketten zien."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"toegang tot SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Geeft een app toegang tot SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tik twee keer voor zoomregeling"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Kan widget niet toevoegen."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ga"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 0220ce3..69ca39c 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ଡିଲିଟ୍ କରନ୍ତୁ"</string>
<string name="inputMethod" msgid="1784759500516314751">"ଇନପୁଟ୍ ପଦ୍ଧତି"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ଟେକ୍ସଟ୍ କାର୍ଯ୍ୟ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ଷ୍ଟୋରେଜ୍ ସ୍ପେସ୍ ଶେଷ ହେବାରେ ଲାଗିଛି"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"କିଛି ସିଷ୍ଟମ ପ୍ରକାର୍ଯ୍ୟ କାମ କରିନପାରେ"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ସିଷ୍ଟମ୍ ପାଇଁ ପ୍ରର୍ଯ୍ୟାପ୍ତ ଷ୍ଟୋରେଜ୍ ନାହିଁ। ସୁନିଶ୍ଚିତ କରନ୍ତୁ ଯେ, ଆପଣଙ୍କ ପାଖରେ 250MB ଖାଲି ଜାଗା ଅଛି ଏବଂ ପୁନଃ ଆରମ୍ଭ କରନ୍ତୁ।"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ଆପ୍ ପାଇଁ ବ୍ୟାଟେରୀ ଅନୁକୂଳନ ଏଡ଼ାଇବାର ଅନୁମତି ମାଗିବା ନିମନ୍ତେ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ସବୁ ପ୍ୟାକେଜ୍ ବିଷୟରେ କ୍ୱେରୀ କରନ୍ତୁ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ଇନଷ୍ଟଲ୍ କରାଯାଇଥିବା ସମସ୍ତ ପ୍ୟାକେଜକୁ ଦେଖିବା ପାଇଁ ଏକ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApiଗୁଡ଼ିକୁ ଆକ୍ସେସ କରନ୍ତୁ"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApiଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏକ ଆପ୍ଲିକେସନକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍ କରନ୍ତୁ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 7a3bc33..864dda2 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ਮਿਟਾਓ"</string>
<string name="inputMethod" msgid="1784759500516314751">"ਇਨਪੁੱਟ ਵਿਧੀ"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"ਟੈਕਸਟ ਕਿਰਿਆਵਾਂ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ਸਟੋਰੇਜ ਦੀ ਜਗ੍ਹਾ ਖਤਮ ਹੋ ਰਹੀ ਹੈ"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ਕੁਝ ਸਿਸਟਮ ਫੰਕਸ਼ਨ ਕੰਮ ਨਹੀਂ ਵੀ ਕਰ ਸਕਦੇ"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ਸਿਸਟਮ ਲਈ ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੇ ਕੋਲ 250MB ਖਾਲੀ ਜਗ੍ਹਾ ਹੈ ਅਤੇ ਮੁੜ-ਚਾਲੂ ਕਰੋ।"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ਕਿਸੇ ਐਪ ਨੂੰ ਉਸ ਵਾਸਤੇ ਬੈਟਰੀ ਸੁਯੋਗਤਾਵਾਂ ਨੂੰ ਅਣਡਿੱਠ ਕਰਨ ਲਈ ਇਜਾਜ਼ਤ ਵਾਸਤੇ ਪੁੱਛਣ ਲਈ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ਸਾਰੇ ਪੈਕੇਜਾਂ ਬਾਰੇ ਪੁੱਛਗਿੱਛ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ਇਸ ਨਾਲ ਐਪ ਨੂੰ ਸਥਾਪਤ ਕੀਤੇ ਸਾਰੇ ਪੈਕੇਜ ਦੇਖਣ ਦੀ ਇਜਾਜ਼ਤ ਮਿਲਦੀ ਹੈ।"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis ਤੱਕ ਪਹੁੰਚ"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"ਕਿਸੇ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ SupplementalApis ਤੱਕ ਪਹੁੰਚ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ।"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ਜ਼ੂਮ ਕੰਟਰੋਲ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਨਹੀਂ ਹੋ ਸਕਿਆ।"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ਜਾਓ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index bbc1867..4d9a07c 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Usuń"</string>
<string name="inputMethod" msgid="1784759500516314751">"Sposób wprowadzania tekstu"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Działania na tekście"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Kończy się miejsce"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Niektóre funkcje systemu mogą nie działać"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Za mało pamięci w systemie. Upewnij się, że masz 250 MB wolnego miejsca i uruchom urządzenie ponownie."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Zezwala aplikacji na proszenie o uprawnienia do ignorowania optymalizacji wykorzystania baterii w przypadku danej aplikacji."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"zapytanie o wszystkie pakiety"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Pozwala aplikacji wyświetlać wszystkie zainstalowane pakiety."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"dostęp do SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Zezwala na dostęp aplikacji do SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dotknij dwukrotnie, aby sterować powiększeniem"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nie można dodać widżetu."</string>
<string name="ime_action_go" msgid="5536744546326495436">"OK"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 3835310..7b7fd55 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Excluir"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de entrada"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Ações de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Pouco espaço de armazenamento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema podem não funcionar"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite que um app peça permissão para ignorar as otimizações de bateria para esse app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar todos os pacotes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite que um app veja todos os pacotes instalados."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"acessar SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite que um aplicativo tenha acesso a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Toque duas vezes para ter controle do zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Não foi possível adicionar widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c2df19a..b4c191d 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Eliminar"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de entrada"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Acções de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Está quase sem espaço de armazenamento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema poderão não funcionar"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não existe armazenamento suficiente para o sistema. Certifique-se de que tem 250 MB de espaço livre e reinicie."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite que uma app solicite autorização para ignorar as otimizações da bateria para a mesma."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar todos os pacotes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite a uma app ver todos os pacotes instalados."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"aceder a SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite a uma aplicação aceder a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tocar duas vezes para controlar o zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Não foi possível adicionar widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 3835310..7b7fd55 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Excluir"</string>
<string name="inputMethod" msgid="1784759500516314751">"Método de entrada"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Ações de texto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Pouco espaço de armazenamento"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Algumas funções do sistema podem não funcionar"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite que um app peça permissão para ignorar as otimizações de bateria para esse app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"consultar todos os pacotes"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite que um app veja todos os pacotes instalados."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"acessar SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite que um aplicativo tenha acesso a SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Toque duas vezes para ter controle do zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Não foi possível adicionar widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Ir"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 4469a67..ffa84e3 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Ștergeți"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metodă de intrare"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Acțiuni pentru text"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Spațiul de stocare aproape ocupat"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Este posibil ca unele funcții de sistem să nu funcționeze"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Spațiu de stocare insuficient pentru sistem. Asigurați-vă că aveți 250 MB de spațiu liber și reporniți."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Permite unei aplicații să solicite permisiunea de a ignora optimizările bateriei pentru aplicația respectivă."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"să interogheze toate pachetele"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Permite unei aplicații să vadă toate pachetele instalate."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"să acceseze SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Permite unei aplicații să acceseze SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Apăsați de două ori pentru a controla mărirea/micșorarea"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nu s-a putut adăuga widgetul."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Accesați"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 6b874b9..2993b48 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Удалить"</string>
<string name="inputMethod" msgid="1784759500516314751">"Способ ввода"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Операции с текстом"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Недостаточно памяти"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Некоторые функции могут не работать"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Недостаточно свободного места для системы. Освободите не менее 250 МБ дискового пространства и перезапустите устройство."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Разрешает приложению игнорировать ограничение на расход заряда батареи."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"Запрос информации обо всех пакетах"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Приложение сможет просматривать все установленные пакеты."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"Доступ к SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Приложение сможет получать доступ к SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Нажмите дважды для изменения масштаба"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Не удалось добавить виджет."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Выбрать"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 3443681..f243dab 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"මකන්න"</string>
<string name="inputMethod" msgid="1784759500516314751">"ආදාන ක්රමය"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"පෙළ ක්රියාවන්"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"ආචයනය ඉඩ ප්රමාණය අඩු වී ඇත"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"සමහර පද්ධති කාර්යයන් ක්රියා නොකරනු ඇත"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"පද්ධතිය සඳහා ප්රමාණවත් ඉඩ නොමැත. ඔබට 250MB නිදහස් ඉඩක් තිබෙන ඔබට තිබෙන බව සහතික කරගෙන නැවත උත්සාහ කරන්න."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"යෙදුමකට එම යෙදුම සඳහා බැටරි ප්රශස්තකරණ නොසලකා හැරීමට අවසර ඉල්ලීමට ඉඩ දෙයි."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"සියලු පැකේජ විමසන්න"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ස්ථාපනය කර ඇති සියලු පැකේජ බැලීමට යෙදුමකට ඉඩ දෙයි."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis වෙත ප්රවේශ වන්න"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApis වෙත ප්රවේශ වීමට යෙදුමකට ඉඩ දෙයි."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"විශාලන පාලක සඳහා දෙවතාවක් තට්ටු කරන්න"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"විජටය එකතු කිරීමට නොහැකි විය."</string>
<string name="ime_action_go" msgid="5536744546326495436">"යන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 517ce6c..f2b3ae1 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Odstrániť"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metóda vstupu"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Operácie s textom"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nedostatok ukladacieho priestoru"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Niektoré systémové funkcie nemusia fungovať"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"V úložisku nie je dostatok voľného miesta pre systém. Zaistite, aby ste mali 250 MB voľného miesta a zariadenie reštartujte."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Umožňuje aplikácii požiadať o povolenie ignorovať optimalizácie výdrže batérie pre danú aplikáciu."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"dopytovať všetky balíky"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Povoľuje aplikácii čítať všetky nainštalované balíky."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"prístup k rozhraniam SupplementalApi"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Umožňuje aplikácii získať prístup k rozhraniam SupplementalApi."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dvojitým klepnutím môžete ovládať priblíženie"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Miniaplikáciu sa nepodarilo pridať."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Hľadať"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 7bcc257..0d71d3b 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Izbriši"</string>
<string name="inputMethod" msgid="1784759500516314751">"Način vnosa"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Besedilna dejanja"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Prostor za shranjevanje bo pošel"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Nekatere sistemske funkcije morda ne delujejo"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"V shrambi ni dovolj prostora za sistem. Sprostite 250 MB prostora in znova zaženite napravo."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Aplikaciji dovoljuje, da vpraša za dovoljenje, ali naj prezre optimizacije baterije."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"poizvedovanje po vseh paketih"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Aplikaciji dovoli, da vidi vse nameščene pakete."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"dostop do SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Aplikaciji omogoča dostop do SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tapnite dvakrat za nadzor povečave/pomanjšave"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Pripomočka ni bilo mogoče dodati."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Pojdi"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 0737100..3050c84 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Fshi"</string>
<string name="inputMethod" msgid="1784759500516314751">"Metoda e hyrjes"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Veprimet e tekstit"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Hapësira ruajtëse po mbaron"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Disa funksione të sistemit mund të mos punojnë"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nuk ka hapësirë të mjaftueshme ruajtjeje për sistemin. Sigurohu që të kesh 250 MB hapësirë të lirë dhe pastaj të rifillosh."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Lejon që një aplikacion të kërkojë leje për të shpërfillur optimizimet e baterisë për atë aplikacion."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"kërko të gjitha paketat"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Lejon një aplikacion të shikojë të gjitha paketat e instaluara."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"qasje te SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Lejon një aplikacion të ketë qasje në SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Trokit dy herë për të kontrolluar zmadhimin"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nuk mundi të shtonte miniaplikacion."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Shko"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index e117bda..c66bb8c 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1207,6 +1207,10 @@
<string name="deleteText" msgid="4200807474529938112">"Избриши"</string>
<string name="inputMethod" msgid="1784759500516314751">"Метод уноса"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Радње у вези са текстом"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Меморијски простор је на измаку"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Неке системске функције можда не функционишу"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Нема довољно меморијског простора за систем. Уверите се да имате 250 MB слободног простора и поново покрените."</string>
@@ -1511,6 +1515,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Дозвољава апликацији да тражи дозволу за игнорисање оптимизација батерије за ту апликацију."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"слање упита за све пакете"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Дозвољава апликацији да види све инсталиране пакете."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"приступ ставци SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Дозвољава апликацији да приступа ставци SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Додирните двапут за контролу зумирања"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Није могуће додати виџет."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Иди"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 90de245..bb40f87 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Ta bort"</string>
<string name="inputMethod" msgid="1784759500516314751">"Indatametod"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Textåtgärder"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lagringsutrymmet börjar ta slut"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Det kan hända att vissa systemfunktioner inte fungerar"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det finns inte tillräckligt med utrymme för systemet. Kontrollera att du har ett lagringsutrymme på minst 250 MB och starta om."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Appen får be om tillstånd att ignorera batterioptimering."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"fråga alla paket"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Tillåter att en app ser alla installerade paket."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"åtkomst till SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Tillåter att en app får åtkomst till SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Peka två gånger för zoomkontroll"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Det gick inte att lägga till widgeten."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Kör"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index ec24102..58e73ca 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Futa"</string>
<string name="inputMethod" msgid="1784759500516314751">"Mbinu ya uingizaji"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Vitendo vya maandishi"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nafasi ya kuhifadhi inakaribia kujaa"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Hifadhi haitoshi kwa ajili ya mfumo. Hakikisha una MB 250 za nafasi ya hifadhi isiyotumika na uanzishe upya."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Huruhusu programu kuomba ruhusa ya kupuuza uimarishaji wa betri katika programu yako."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"kutuma hoja kwa vifurushi vyote"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Huruhusu programu kuona vifurushi vyote vilivyosakinishwa."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"kufikia SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Huruhusu programu kufikia SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Gusa mara mbili kwa udhibiti wa kuza"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Haikuweza kuongeza wijeti."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Nenda"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index e7ea59d..f4f37a6 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"நீக்கு"</string>
<string name="inputMethod" msgid="1784759500516314751">"உள்ளீட்டு முறை"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"உரை நடவடிக்கைகள்"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"சேமிப்பிடம் குறைகிறது"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"சில அமைப்பு செயல்பாடுகள் வேலை செய்யாமல் போகலாம்"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"முறைமையில் போதுமான சேமிப்பகம் இல்லை. 250மெ.பை. அளவு காலி இடவசதி இருப்பதை உறுதிசெய்து மீண்டும் தொடங்கவும்."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"பயன்பாட்டிற்கான பேட்டரி மேம்படுத்தல்களைப் புறக்கணிப்பதற்கான அனுமதியைக் கோர, ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"அனைத்துப் பேக்கேஜ்களையும் பார்க்க அனுமதித்தல்"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"நிறுவப்பட்டுள்ள அனைத்துப் பேக்கேஜ்களையும் பார்ப்பதற்கு ஆப்ஸை அனுமதிக்கும்."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApiகளை அணுகுதல்"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApiகளை அணுக ஆப்ஸை அனுமதிக்கும்."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"அளவை மாற்றுவதற்கான கட்டுப்பாட்டிற்கு, இருமுறை தட்டவும்"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"விட்ஜெட்டைச் சேர்க்க முடியவில்லை."</string>
<string name="ime_action_go" msgid="5536744546326495436">"செல்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 176d845..46fe375 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -174,7 +174,7 @@
<string name="httpErrorTooManyRequests" msgid="2149677715552037198">"చాలా ఎక్కువ రిక్వెస్ట్లు ప్రాసెస్ చేయబడుతున్నాయి. తర్వాత మళ్లీ ప్రయత్నించండి."</string>
<string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g>కు సైన్ఇన్ ఎర్రర్"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"సింక్"</string>
- <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"సమకాలీకరించడం సాధ్యపడదు"</string>
+ <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"సింక్ చేయడం సాధ్యపడదు"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"చాలా ఎక్కువ <xliff:g id="CONTENT_TYPE">%s</xliff:g> తొలగించడానికి ప్రయత్నించారు."</string>
<string name="low_memory" product="tablet" msgid="5557552311566179924">"టాబ్లెట్ నిల్వ నిండింది. స్థలాన్ని ఖాళీ చేయడానికి కొన్ని ఫైళ్లను తొలగించండి."</string>
<string name="low_memory" product="watch" msgid="3479447988234030194">"వాచ్ నిల్వ నిండింది. స్థలాన్ని ఖాళీ చేయడానికి కొన్ని ఫైళ్లను తొలగించండి."</string>
@@ -224,7 +224,7 @@
<string name="silent_mode_vibrate" msgid="8821830448369552678">"రింగర్ వైబ్రేట్లో ఉంది"</string>
<string name="silent_mode_ring" msgid="6039011004781526678">"రింగర్ ఆన్లో ఉంది"</string>
<string name="reboot_to_update_title" msgid="2125818841916373708">"Android సిస్టమ్ అప్డేట్"</string>
- <string name="reboot_to_update_prepare" msgid="6978842143587422365">"నవీకరించడానికి సిద్ధం చేస్తోంది…"</string>
+ <string name="reboot_to_update_prepare" msgid="6978842143587422365">"అప్డేట్ చేయడానికి సిద్ధం చేస్తోంది…"</string>
<string name="reboot_to_update_package" msgid="4644104795527534811">"అప్డేట్ ప్యాకేజీని ప్రాసెస్ చేస్తోంది…"</string>
<string name="reboot_to_update_reboot" msgid="4474726009984452312">"పునఃప్రారంభించబడుతోంది…"</string>
<string name="reboot_to_reset_title" msgid="2226229680017882787">"ఫ్యాక్టరీ డేటా రీసెట్ చేయండి"</string>
@@ -369,7 +369,7 @@
<string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"సెల్ ప్రసార మెసేజ్లను చదవడం"</string>
<string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"మీ పరికరం స్వీకరించిన సెల్ ప్రసార మెసేజ్లను చదవడానికి యాప్ను అనుమతిస్తుంది. ఎమర్జెన్సీ పరిస్థితుల గురించి మిమ్మల్ని హెచ్చరించడానికి కొన్ని లొకేషన్లలో సెల్ ప్రసార అలర్ట్లు డెలివరీ చేయబడతాయి. ఎమర్జెన్సీ సెల్ ప్రసార అలర్ట్ను స్వీకరించినప్పుడు హానికరమైన యాప్లు మీ పరికరం పనితీరుకు లేదా నిర్వహణకు ఆటంకం కలిగించే అవకాశం ఉంది."</string>
<string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"చందా చేయబడిన ఫీడ్లను చదవడం"</string>
- <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"ప్రస్తుతం సమకాలీకరించిన ఫీడ్ల గురించి వివరాలను పొందడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"ప్రస్తుతం సింక్ చేసిన ఫీడ్ల గురించి వివరాలను పొందడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_sendSms" msgid="7757368721742014252">"SMS మెసేజ్లను పంపడం, వీక్షించడం"</string>
<string name="permdesc_sendSms" msgid="6757089798435130769">"SMS మెసేజ్లు పంపడానికి యాప్ను అనుమతిస్తుంది. దీని వలన ఊహించని ఛార్జీలు విధించబడవచ్చు. హానికరమైన యాప్లు మీ నిర్ధారణ లేకుండానే మెసేజ్లను పంపడం ద్వారా మీకు డబ్బు ఖర్చయ్యేలా చేయవచ్చు."</string>
<string name="permlab_readSms" msgid="5164176626258800297">"మీ టెక్స్ట్ మెసేజ్లు (SMS లేదా MMS) చదవడం"</string>
@@ -403,7 +403,7 @@
<string name="permlab_getPackageSize" msgid="375391550792886641">"యాప్ నిల్వ స్థలాన్ని అంచనా వేయడం"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"యాప్ కోడ్, డేటా మరియు కాష్ పరిమాణాలను తిరిగి పొందడానికి దాన్ని అనుమతిస్తుంది"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"సిస్టమ్ సెట్టింగ్లను మార్చడం"</string>
- <string name="permdesc_writeSettings" msgid="8293047411196067188">"సిస్టమ్ యొక్క సెట్టింగ్ల డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ సిస్టమ్ యొక్క కాన్ఫిగరేషన్ను నాశనం చేయవచ్చు."</string>
+ <string name="permdesc_writeSettings" msgid="8293047411196067188">"సిస్టమ్ యొక్క సెట్టింగ్ల డేటాను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ సిస్టమ్ యొక్క కాన్ఫిగరేషన్ను నాశనం చేయవచ్చు."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"ప్రారంభంలో అమలు చేయడం"</string>
<string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"సిస్టమ్ బూటింగ్ను పూర్తి చేసిన వెంటనే దానికదే ప్రారంభించబడటానికి యాప్ను అనుమతిస్తుంది. ఇది టాబ్లెట్ను ప్రారంభించడానికి ఎక్కువ సమయం పట్టేలా చేయవచ్చు మరియు ఎల్లప్పుడూ అమలు చేయడం ద్వారా మొత్తం టాబ్లెట్ను నెమ్మదిగా పని చేయడానికి యాప్ను అనుమతించేలా చేయవచ్చు."</string>
<string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"సిస్టమ్ బూటింగ్ను పూర్తి చేసిన వెంటనే యాప్ దానికదే ప్రారంభం కావడానికి అనుమతిస్తుంది. ఇది మీ Android TV పరికరం ప్రారంభం కావడానికి ఎక్కువ సమయం పట్టేలా చేయవచ్చు మరియు ఎల్లప్పుడూ అమలు కావడం ద్వారా మొత్తం పరికరం పనితీరును నెమ్మది చేయడానికి యాప్ను అనుమతించవచ్చు."</string>
@@ -417,15 +417,15 @@
<string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"మీ Android TV పరికరంలో నిల్వ చేసిన కాంటాక్ట్లకు సంబంధించిన డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. కాంటాక్ట్లను సృష్టించిన మీ Android TV పరికరంలోని ఖాతాలకు కూడా యాప్లకు యాక్సెస్ ఉంటుంది. ఇందులో మీరు ఇన్స్టాల్ చేసిన యాప్ల ద్వారా సృష్టించబడిన ఖాతాలు ఉండవచ్చు. ఈ అనుమతి, మీ కాంటాక్ట్ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది, హానికరమైన యాప్లు మీకు తెలియకుండానే కాంటాక్ట్ డేటాను షేర్ చేయవచ్చు."</string>
<string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ఫోన్లో నిల్వ చేసిన మీ కాంటాక్ట్లకు సంబంధించిన డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. కాంటాక్ట్లను సృష్టించిన మీ ఫోన్లోని ఖాతాలను కూడా యాప్లు యాక్సెస్ చేయగలవు. ఇందులో మీరు ఇన్స్టాల్ చేసిన యాప్ల ద్వారా సృష్టించబడిన ఖాతాలు ఉండవచ్చు. ఈ అనుమతి, మీ కాంటాక్ట్ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది, హానికరమైన యాప్లు మీకు తెలియకుండానే కాంటాక్ట్ డేటాను షేర్ చేయవచ్చు."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"మీ కాంటాక్ట్లను సవరించడం"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"మీ టాబ్లెట్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"మీ Android TV పరికరంలో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
- <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"మీ ఫోన్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"మీ టాబ్లెట్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"మీ Android TV పరికరంలో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"మీ ఫోన్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"కాల్ లాగ్ను చదవడం"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ఈ యాప్ మీ కాల్ చరిత్రను చదవగలదు."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"కాల్ లాగ్ను రాయడం"</string>
- <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్ల గురించిన డేటాతో సహా మీ టాబ్లెట్ యొక్క కాల్ లాగ్ను సవరించడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను ఎరేజ్ చేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
- <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్కు సంబంధించిన డేటాతో సహా మీ Android TV పరికరం కాల్ లాగ్ను సవరించడానికి యాప్ని అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను తీసివేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
- <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్ల గురించిన డేటాతో సహా మీ ఫోన్ యొక్క కాల్ లాగ్ను సవరించడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను ఎరేజ్ చేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
+ <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్ల గురించిన డేటాతో సహా మీ టాబ్లెట్ యొక్క కాల్ లాగ్ను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను ఎరేజ్ చేయడానికి లేదా ఎడిట్ చేయడానికి దీన్ని ఉపయోగించవచ్చు."</string>
+ <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్కు సంబంధించిన డేటాతో సహా మీ Android TV పరికరం కాల్ లాగ్ను ఎడిట్ చేయడానికి యాప్ని అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను తీసివేయడానికి లేదా ఎడిట్ చేయడానికి దీన్ని ఉపయోగించవచ్చు."</string>
+ <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"ఇన్కమింగ్ మరియు అవుట్గోయింగ్ కాల్స్ల గురించిన డేటాతో సహా మీ ఫోన్ యొక్క కాల్ లాగ్ను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు మీ కాల్ లాగ్ను ఎరేజ్ చేయడానికి లేదా ఎడిట్ చేయడానికి దీన్ని ఉపయోగించవచ్చు."</string>
<string name="permlab_bodySensors" msgid="3411035315357380862">"శరీర సెన్సార్లను (గుండె స్పందన రేటు మానిటర్ల వంటివి) యాక్సెస్ చేయండి"</string>
<string name="permdesc_bodySensors" product="default" msgid="3208940894182188063">"గుండె స్పందన రేటు, ఉష్ణోగ్రత, రక్తంలో ఆక్సిజన్ శాతం మొదలైన శరీర సెన్సార్ల నుండి డేటాను యాక్సెస్ చేయండి."</string>
<string name="permlab_bodySensors_background" msgid="4352831883331744370">"బ్యాక్గ్రౌండ్లో శరీర సెన్సార్లను (గుండె రేటు మానిటర్స్) యాక్సెస్ చేయండి"</string>
@@ -447,7 +447,7 @@
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"బ్యాక్గ్రౌండ్లో లొకేషన్ను యాక్సెస్ చేయి"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"యాప్ ఉపయోగంలో లేనప్పటికీ కూడా, ఈ యాప్, లొకేషన్ను ఎప్పుడైనా యాక్సెస్ చేయగలదు."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"మీ ఆడియో సెట్టింగ్లను మార్చడం"</string>
- <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"వాల్యూమ్ మరియు అవుట్పుట్ కోసం ఉపయోగించాల్సిన స్పీకర్ వంటి సార్వజనీన ఆడియో సెట్టింగ్లను సవరించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"వాల్యూమ్ మరియు అవుట్పుట్ కోసం ఉపయోగించాల్సిన స్పీకర్ వంటి సార్వజనీన ఆడియో సెట్టింగ్లను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ఆడియోను రికార్డ్ చేయడం"</string>
<string name="permdesc_recordAudio" msgid="5857246765327514062">"యాప్ ఉపయోగంలో ఉన్నపుడు మైక్రోఫోన్ను ఉపయోగించి ఈ యాప్, ఆడియోను రికార్డ్ చేయగలదు."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"బ్యాక్గ్రౌండ్లో ఆడియోను రికార్డ్ చేయగలదు"</string>
@@ -566,11 +566,11 @@
<string name="permlab_useFingerprint" msgid="1001421069766751922">"వేలిముద్ర హార్డ్వేర్ని ఉపయోగించడానికి అనుమతి"</string>
<string name="permdesc_useFingerprint" msgid="412463055059323742">"ప్రామాణీకరణ కోసం వేలిముద్ర హార్డ్వేర్ను ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది"</string>
<string name="permlab_audioWrite" msgid="8501705294265669405">"మీ సంగీత సేకరణను ఎడిట్ చేయండి"</string>
- <string name="permdesc_audioWrite" msgid="8057399517013412431">"మీ సంగీత సేకరణని సవరించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_audioWrite" msgid="8057399517013412431">"మీ సంగీత సేకరణని ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_videoWrite" msgid="5940738769586451318">"మీ వీడియో సేకరణను ఎడిట్ చేయండి"</string>
- <string name="permdesc_videoWrite" msgid="6124731210613317051">"మీ వీడియో సేకరణను సవరించడానికి యాప్ని అనుమతిస్తుంది."</string>
+ <string name="permdesc_videoWrite" msgid="6124731210613317051">"మీ వీడియో సేకరణను ఎడిట్ చేయడానికి యాప్ని అనుమతిస్తుంది."</string>
<string name="permlab_imagesWrite" msgid="1774555086984985578">"మీ ఫోటో సేకరణను ఎడిట్ చేయండి"</string>
- <string name="permdesc_imagesWrite" msgid="5195054463269193317">"మీ ఫోటో సేకరణను సవరించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"మీ ఫోటో సేకరణను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_mediaLocation" msgid="7368098373378598066">"మీ మీడియా సేకరణ నుండి లొకేషన్లను చదవండి"</string>
<string name="permdesc_mediaLocation" msgid="597912899423578138">"మీ మీడియా సేకరణ నుండి లొకేషన్లను చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"బయోమెట్రిక్స్ను ఉపయోగించండి"</string>
@@ -678,7 +678,7 @@
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"సింక్ సెట్టింగ్లను చదవగలగడం"</string>
<string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ఖాతా యొక్క సింక్ సెట్టింగ్లను చదవడానికి యాప్ను అనుమతిస్తుంది. ఉదాహరణకు, వ్యక్తుల యాప్ ఖాతాతో సమకాలీకరించబడాలా లేదా అనే విషయాన్ని ఇది నిశ్చయించవచ్చు."</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"\'సింక్\'ను ఆన్, ఆఫ్ల మధ్య టోగుల్ చేయడం"</string>
- <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ఖాతా యొక్క సింక్ సెట్టింగ్లను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఉదాహరణకు, ఇది ఒక ఖాతాతో వ్యక్తుల యాప్ యొక్క సింక్ను ప్రారంభించడానికి ఉపయోగించబడవచ్చు."</string>
+ <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ఖాతా యొక్క సింక్ సెట్టింగ్లను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఉదాహరణకు, ఇది ఒక ఖాతాతో వ్యక్తుల యాప్ యొక్క సింక్ను ప్రారంభించడానికి ఉపయోగించబడవచ్చు."</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"సింక్ గణాంకాలను చదవగలగడం"</string>
<string name="permdesc_readSyncStats" msgid="3867809926567379434">"ఖాతా యొక్క సింక్ గణాంకాలను అలాగే సింక్ ఈవెంట్ల చరిత్రను మరియు ఎంత డేటా సమకాలీకరించబడింది అనేవాటిని చదవడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_sdcardRead" msgid="5791467020950064920">"మీ షేర్ చేసిన నిల్వ యొక్క కంటెంట్లను చదువుతుంది"</string>
@@ -704,7 +704,7 @@
<string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"నెట్వర్క్ విధానాన్ని నిర్వహించడం"</string>
<string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్వర్క్ విధానాలను నిర్వహించడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"నెట్వర్క్ వినియోగ అకౌంటింగ్ను సవరించడం"</string>
- <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్లలో నెట్వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని సవరించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string>
+ <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్లలో నెట్వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string>
<string name="permlab_accessNotifications" msgid="7130360248191984741">"నోటిఫికేషన్లను యాక్సెస్ చేయడం"</string>
<string name="permdesc_accessNotifications" msgid="761730149268789668">"నోటిఫికేషన్లను, ఇతర యాప్ల ద్వారా పోస్ట్ చేయబడిన వాటిని తిరిగి పొందడానికి, పరిశీలించడానికి మరియు క్లియర్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"నోటిఫికేషన్ పరిశీలన సేవకు అనుబంధించడం"</string>
@@ -718,7 +718,7 @@
<string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"నెట్వర్క్ పరిస్థితులపై పరిశీలనల గురించి తెలుసుకోవడం"</string>
<string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"నెట్వర్క్ పరిస్థితులపై పరిశీలనల గురించి తెలుసుకోవడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండకూడదు."</string>
<string name="permlab_setInputCalibration" msgid="932069700285223434">"ఇన్పుట్ పరికరం క్రమాంకనాన్ని మార్చండి"</string>
- <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"టచ్ స్క్రీన్ యొక్క క్రమాంకన పరామితులను సవరించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండదు."</string>
+ <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"టచ్ స్క్రీన్ యొక్క క్రమాంకన పరామితులను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండదు."</string>
<string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"DRM ప్రమాణపత్రాలను యాక్సెస్ చేయడం"</string>
<string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"DRM ప్రమాణపత్రాలను కేటాయించడానికి మరియు ఉపయోగించడానికి యాప్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండదు."</string>
<string name="permlab_handoverStatus" msgid="7620438488137057281">"Android Beam బదిలీ స్టేటస్ని స్వీకరించడం"</string>
@@ -1029,14 +1029,14 @@
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"బ్రౌజర్ సందర్శించిన అన్ని URLల చరిత్ర గురించి మరియు అన్ని బ్రౌజర్ బుక్మార్క్ల గురించి చదవడానికి యాప్ను అనుమతిస్తుంది. గమనిక: ఈ అనుమతి మూడవ పక్షం బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్ల ద్వారా అమలు చేయబడకపోవచ్చు."</string>
<string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"వెబ్ బుక్మార్క్లు మరియు చరిత్రను రాయడం"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"మీ టాబ్లెట్లో నిల్వ చేయబడిన బ్రౌజర్ హిస్టరీని, బుక్మార్క్లను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను ఎరేజ్ చేయడానికి లేదా ఎడిట్ చేయడానికి యాప్ను అనుమతించవచ్చు. గమనిక: ఈ అనుమతిని థర్డ్ పార్టీ బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్లు అమలు చేయకపోవచ్చు."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"మీ Android TV పరికరంలో నిల్వ చేసిన బ్రౌజర్ చరిత్ర లేదా బుక్మార్క్లను సవరించడానికి యాప్ని అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను తీసివేయడానికి లేదా సవరించడానికి యాప్ని అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ-పక్ష బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్ల ద్వారా అమలు కాకపోవచ్చు."</string>
- <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"మీ ఫోన్లో నిల్వ చేయబడిన బ్రౌజర్ చరిత్రను లేదా బుక్మార్క్లను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను ఎరేజ్ చేయడానికి లేదా సవరించడానికి యాప్ను అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ పక్షం బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్ల ద్వారా అమలు చేయబడకపోవచ్చు."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"మీ Android TV పరికరంలో నిల్వ చేసిన బ్రౌజర్ చరిత్ర లేదా బుక్మార్క్లను ఎడిట్ చేయడానికి యాప్ని అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను తీసివేయడానికి లేదా ఎడిట్ చేయడానికి యాప్ని అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ-పక్ష బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్ల ద్వారా అమలు కాకపోవచ్చు."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"మీ ఫోన్లో నిల్వ చేయబడిన బ్రౌజర్ చరిత్రను లేదా బుక్మార్క్లను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను ఎరేజ్ చేయడానికి లేదా ఎడిట్ చేయడానికి యాప్ను అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ పక్షం బ్రౌజర్లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్ల ద్వారా అమలు చేయబడకపోవచ్చు."</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"అలారం సెట్ చేయడం"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"ఇన్స్టాల్ చేయబడిన అలారం గడియారం యాప్లో అలారంను సెట్ చేయడానికి యాప్ను అనుమతిస్తుంది. కొన్ని అలారం గల గడియారం యాప్లు ఈ ఫీచర్ను అమలు చేయకపోవచ్చు."</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"వాయిస్ మెయిల్ను జోడించడం"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"మీ వాయిస్ మెయిల్ ఇన్బాక్స్కు మెసేజ్లను జోడించడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"బ్రౌజర్ భౌగోళిక స్థానం అనుమతులను సవరించడం"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"బ్రౌజర్ యొక్క భౌగోళిక లొకేషన్ అనుమతులను సవరించడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు ఏకపక్ష వెబ్ సైట్లకు లొకేషన్ సమాచారాన్ని అనుమతించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"బ్రౌజర్ యొక్క భౌగోళిక లొకేషన్ అనుమతులను ఎడిట్ చేయడానికి యాప్ను అనుమతిస్తుంది. హానికరమైన యాప్లు ఏకపక్ష వెబ్ సైట్లకు లొకేషన్ సమాచారాన్ని అనుమతించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
<string name="save_password_message" msgid="2146409467245462965">"మీరు బ్రౌజర్ ఈ పాస్వర్డ్ను గుర్తుపెట్టుకోవాలని కోరుకుంటున్నారా?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"ఇప్పుడు కాదు"</string>
<string name="save_password_remember" msgid="6490888932657708341">"గుర్తుంచుకో"</string>
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"తొలగించు"</string>
<string name="inputMethod" msgid="1784759500516314751">"ఇన్పుట్ పద్ధతి"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"వచనానికి సంబంధించిన చర్యలు"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"నిల్వ ఖాళీ అయిపోతోంది"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"కొన్ని సిస్టమ్ కార్యాచరణలు పని చేయకపోవచ్చు"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"సిస్టమ్ కోసం తగినంత నిల్వ లేదు. మీకు 250MB ఖాళీ స్థలం ఉందని నిర్ధారించుకుని, పునఃప్రారంభించండి."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ఆ యాప్ కోసం బ్యాటరీ అనుకూలీకరణలు విస్మరించేలా అనుమతి కోరడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"అన్ని ప్యాకేజీలను క్వెరీ చేయండి"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ఇన్స్టాల్ చేసిన అన్ని ప్యాకేజీలను చూడటానికి యాప్ను అనుమతించండి."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApisని యాక్సెస్ చేయండి"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"SupplementalApisని యాక్సెస్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"జూమ్ నియంత్రణ కోసం రెండుసార్లు నొక్కండి"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"విడ్జెట్ను జోడించడం సాధ్యపడలేదు."</string>
<string name="ime_action_go" msgid="5536744546326495436">"వెళ్లు"</string>
@@ -1871,7 +1877,7 @@
<string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"ప్రస్తుత పిన్"</string>
<string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"కొత్త పిన్"</string>
<string name="restr_pin_confirm_pin" msgid="7143161971614944989">"కొత్త పిన్ను నిర్ధారించండి"</string>
- <string name="restr_pin_create_pin" msgid="917067613896366033">"నియంత్రణలను సవరించడానికి పిన్ను రూపొందించండి"</string>
+ <string name="restr_pin_create_pin" msgid="917067613896366033">"నియంత్రణలను ఎడిట్ చేయడానికి పిన్ను రూపొందించండి"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"పిన్లు సరిపోలలేదు. మళ్లీ ప్రయత్నించండి."</string>
<string name="restr_pin_error_too_short" msgid="1547007808237941065">"పిన్ చాలా చిన్నదిగా ఉంది. తప్పనిసరిగా కనీసం 4 అంకెలు ఉండాలి."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="4427486903285216153">
@@ -1879,7 +1885,7 @@
<item quantity="one">1 సెకనులో మళ్లీ ప్రయత్నించండి</item>
</plurals>
<string name="restr_pin_try_later" msgid="5897719962541636727">"తర్వాత మళ్లీ ప్రయత్నించండి"</string>
- <string name="immersive_cling_title" msgid="2307034298721541791">"పూర్తి స్క్రీన్లో వీక్షిస్తున్నారు"</string>
+ <string name="immersive_cling_title" msgid="2307034298721541791">"ఫుల్-స్క్రీన్లో వీక్షిస్తున్నారు"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"నిష్క్రమించడానికి, పై నుండి క్రిందికి స్వైప్ చేయండి."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"అర్థమైంది"</string>
<string name="done_label" msgid="7283767013231718521">"పూర్తయింది"</string>
@@ -2148,8 +2154,8 @@
<string name="mime_type_image_ext" msgid="5743552697560999471">"<xliff:g id="EXTENSION">%1$s</xliff:g> చిత్రం"</string>
<string name="mime_type_compressed" msgid="8737300936080662063">"ఆర్కైవ్"</string>
<string name="mime_type_compressed_ext" msgid="4775627287994475737">"<xliff:g id="EXTENSION">%1$s</xliff:g> ఆర్కైవ్"</string>
- <string name="mime_type_document" msgid="3737256839487088554">"పత్రం"</string>
- <string name="mime_type_document_ext" msgid="2398002765046677311">"<xliff:g id="EXTENSION">%1$s</xliff:g> పత్రం"</string>
+ <string name="mime_type_document" msgid="3737256839487088554">"డాక్యుమెంట్"</string>
+ <string name="mime_type_document_ext" msgid="2398002765046677311">"<xliff:g id="EXTENSION">%1$s</xliff:g> డాక్యుమెంట్"</string>
<string name="mime_type_spreadsheet" msgid="8188407519131275838">"స్ప్రెడ్షీట్"</string>
<string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> స్ప్రెడ్షీట్"</string>
<string name="mime_type_presentation" msgid="1145384236788242075">"ప్రదర్శన"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 30b3d0b..f485e85 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"ลบ"</string>
<string name="inputMethod" msgid="1784759500516314751">"วิธีป้อนข้อมูล"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"การทำงานของข้อความ"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"พื้นที่จัดเก็บเหลือน้อย"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"บางฟังก์ชันระบบอาจไม่ทำงาน"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"พื้นที่เก็บข้อมูลไม่เพียงพอสำหรับระบบ โปรดตรวจสอบว่าคุณมีพื้นที่ว่าง 250 MB แล้วรีสตาร์ท"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"อนุญาตให้แอปขอสิทธิ์เพิกเฉยต่อการเพิ่มประสิทธิภาพแบตเตอรี่สำหรับแอปนั้น"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ค้นหาแพ็กเกจทั้งหมด"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"อนุญาตให้แอปดูแพ็กเกจที่ติดตั้งไว้ทั้งหมด"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"เข้าถึง SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"อนุญาตให้แอปพลิเคชันเข้าถึง SupplementalApis"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"แตะสองครั้งเพื่อควบคุมการซูม"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ไม่สามารถเพิ่มวิดเจ็ต"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ไป"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c0bbc69..6448755 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"I-delete"</string>
<string name="inputMethod" msgid="1784759500516314751">"Pamamaraan ng pag-input"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Pagkilos ng teksto"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Nauubusan na ang puwang ng storage"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Maaaring hindi gumana nang tama ang ilang paggana ng system"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Walang sapat na storage para sa system. Tiyaking mayroon kang 250MB na libreng espasyo at i-restart."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Pinapayagang humingi ng pahintulot ang isang app na balewalain ang mga pag-optimize ng baterya para sa app na iyon."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"i-query ang lahat ng package"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Nagbibigay-daan sa isang app na makita ang lahat ng naka-install na package."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"access sa SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Nagbibigay-daan sa isang application na i-access ang SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tapikin ng dalawang beses para sa pagkontrol ng zoom"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Hindi maidagdag ang widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Pumunta"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8a86227..e3c20baf 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Sil"</string>
<string name="inputMethod" msgid="1784759500516314751">"Giriş yöntemi"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Metin eylemleri"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Depolama alanı bitiyor"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Bazı sistem işlevleri çalışmayabilir"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Sistem için yeterli depolama alanı yok. 250 MB boş alanınızın bulunduğundan emin olun ve yeniden başlatın."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Bir uygulamanın, kendisi için pil optimizasyonlarını göz ardı etme izni istemesine olanak sağlar."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"tüm paketleri sorgulama"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Uygulamaya tüm yüklü paketleri görme izni verir."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis\'e erişim"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Bir uygulamanın SupplementalApis\'e erişimine izin verir."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Zum denetimi için iki kez dokun"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Widget eklenemedi."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Git"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index f4fb575..c920e64 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1227,6 +1227,10 @@
<string name="deleteText" msgid="4200807474529938112">"Видалити"</string>
<string name="inputMethod" msgid="1784759500516314751">"Метод введення"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Дії з текстом"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Закінчується пам’ять"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Деякі системні функції можуть не працювати"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Недостатньо місця для системи. Переконайтесь, що на пристрої є 250 МБ вільного місця, і повторіть спробу."</string>
@@ -1531,6 +1535,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Додаток зможе запитувати дозвіл ігнорувати оптимізацію використання заряду акумулятора."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"подавати запити на всі пакети"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Дозволяє додатку переглядати всі встановлені пакети."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"Доступ до SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Надає додатку доступ до SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Двічі натис. для кер. масшт."</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Не вдалося додати віджет."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Йти"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 2c712f4..17da4e4 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"حذف کریں"</string>
<string name="inputMethod" msgid="1784759500516314751">"اندراج کا طریقہ"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"متن کی کارروائیاں"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"اسٹوریج کی جگہ ختم ہو رہی ہے"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ممکن ہے سسٹم کے کچھ فنکشنز کام نہ کریں"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"سسٹم کیلئے کافی اسٹوریج نہیں ہے۔ اس بات کو یقینی بنائیں کہ آپ کے پاس 250MB خالی جگہ ہے اور دوبارہ شروع کریں۔"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"اس ایپ کیلئے ایک ایپ کو بیٹری کی کارکردگی بہتر بنانے کو نظر انداز کرنے کی اجازت دیں۔"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"سبھی پیکیجز سے متعلق استفسار کریں"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ایپ کو سبھی انسٹال کردہ پیکیجز دیکھنے کی اجازت دیتا ہے۔"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis تک رسائی حاصل کریں"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"ایپلیکیشن کو SupplementalApis تک رسائی حاصل کرنے کی اجازت دیتا ہے۔"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"زوم کنٹرول کیلئے دوبار تھپتھپائیں"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ویجٹس کو شامل نہیں کرسکا۔"</string>
<string name="ime_action_go" msgid="5536744546326495436">"جائیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 42a88d7..880a22d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"O‘chirish"</string>
<string name="inputMethod" msgid="1784759500516314751">"Kiritish uslubi"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Matn yozish"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Xotirada joy yetarli emas"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Ayrim funksiyalar ishlamasligi mumkin"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Tizim uchun xotirada joy yetarli emas. Avval 250 megabayt joy bo‘shatib, keyin qurilmani o‘chirib yoqing."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Ilovaga batareya quvvatidan xohlagancha foydalanish uchun ruxsat so‘rashga imkon beradi."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"barcha paketlarni chiqarish"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Ilova oʻrnatilgan barcha paketlarni koʻrishiga ruxsat beradi"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"SupplementalApis omboriga ruxsat"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Ilovaga SupplementalApis omboriga ruxsat beradi."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Ko‘lamini o‘zgartirish uchun ikki marta bosing"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Vidjet qo‘shilmadi."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Tanlash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 05899ed..448207e 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Xóa"</string>
<string name="inputMethod" msgid="1784759500516314751">"Phương thức nhập"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Tác vụ văn bản"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Sắp hết dung lượng lưu trữ"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Một số chức năng hệ thống có thể không hoạt động"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Không đủ bộ nhớ cho hệ thống. Đảm bảo bạn có 250 MB dung lượng trống và khởi động lại."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Cho phép ứng dụng hỏi quyền để bỏ qua tối ưu hóa pin cho ứng dụng đó."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"truy vấn tất cả các gói"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Cho phép một ứng dụng xem tất cả các gói đã cài đặt."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"quyền truy cập vào SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Cho phép ứng dụng truy cập vào SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Nhấn hai lần để kiểm soát thu phóng"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Không thể thêm tiện ích."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Đến"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 6acd3e0..6c330f2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"删除"</string>
<string name="inputMethod" msgid="1784759500516314751">"输入法"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"文字操作"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"存储空间不足"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"某些系统功能可能无法正常使用"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系统存储空间不足。请确保您有250MB的可用空间,然后重新启动。"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"允许应用请求相应的权限,以便忽略针对该应用的电池优化。"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"查询所有软件包"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"允许应用查看所有已安装的软件包。"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"访问 SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"允许应用访问 SupplementalApis。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"双击可以进行缩放控制"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"无法添加微件。"</string>
<string name="ime_action_go" msgid="5536744546326495436">"开始"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index bf51c1a..7a468f7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"刪除"</string>
<string name="inputMethod" msgid="1784759500516314751">"輸入法"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"文字操作"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"儲存空間即將用盡"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"部分系統功能可能無法運作"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系統儲存空間不足。請確認裝置有 250 MB 的可用空間,然後重新啟動。"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"允許應用程式要求就該應用程式忽略電池優化。"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"查詢所有套件"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"允許應用程式查看所有已安裝的套件。"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"存取 SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"允許應用程式存取 SupplementalApis。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"輕觸兩下控制縮放"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"無法新增小工具。"</string>
<string name="ime_action_go" msgid="5536744546326495436">"開始"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index bd0b9a2..e570d65 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"刪除"</string>
<string name="inputMethod" msgid="1784759500516314751">"輸入法"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"文字動作"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"儲存空間即將用盡"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"部分系統功能可能無法運作"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"系統儲存空間不足。請確定你已釋出 250MB 的可用空間,然後重新啟動。"</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"允許應用程式要求權限,以便忽略針對該應用程式的電池效能最佳化設定。"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"查詢所有套件"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"允許應用程式查看所有已安裝的套件。"</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"存取 SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"允許應用程式存取 SupplementalApis。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"點兩下以進行縮放控制"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"無法新增小工具。"</string>
<string name="ime_action_go" msgid="5536744546326495436">"開始"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index bf024528..301dced 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1187,6 +1187,10 @@
<string name="deleteText" msgid="4200807474529938112">"Susa"</string>
<string name="inputMethod" msgid="1784759500516314751">"Indlela yokufakwayo"</string>
<string name="editTextMenuTitle" msgid="857666911134482176">"Izenzo zombhalo"</string>
+ <!-- no translation found for input_method_nav_back_button_desc (3655838793765691787) -->
+ <skip />
+ <!-- no translation found for input_method_ime_switch_button_desc (2736542240252198501) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Isikhala sokulondoloza siyaphela"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Eminye imisebenzi yohlelo ingahle ingasebenzi"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Akusona isitoreji esanele sesistimu. Qiniseka ukuthi unesikhala esikhululekile esingu-250MB uphinde uqalise kabusha."</string>
@@ -1491,6 +1495,8 @@
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Ivumela uhlelo lokusebenza ukuthi licele imvume yokuziba ukulungiselela ibhethri yalolo hlelo lokusebenza."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"buza wonke amaphakheji"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Ivumela i-app ibone wonke amaphakheji afakiwe."</string>
+ <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"finyelela i-SupplementalApis"</string>
+ <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"Ivumela i-application ukuba ifinyelele i-SupplementalApis."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Thepha kabili ukuthola ukulawula ukusondeza"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"Yehlulekile ukwengeza i-widget."</string>
<string name="ime_action_go" msgid="5536744546326495436">"Iya"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8696f5a..7916ef4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3355,6 +3355,14 @@
<p>The system will try to respect this, but when not possible will ignore it.
See {@link android.view.View#setPreferKeepClear}. -->
<attr name="preferKeepClear" format="boolean" />
+
+ <!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
+ <p>For a view with active {@link android.view.inputmethod.InputConnection},
+ if auto handwriting initiation is enabled stylus movement within its view boundary
+ will automatically trigger the handwriting mode.
+ <p>This is true by default.
+ See {@link android.view.View#setAutoHandwritingEnabled}. -->
+ <attr name="autoHandwritingEnabled" format="boolean" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5fee1fa..74cd519 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2373,6 +2373,9 @@
<bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
<!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
<string name="config_dreamsDefaultComponent" translatable="false">com.android.deskclock/com.android.deskclock.Screensaver</string>
+ <!-- ComponentNames of the dreams that we should hide -->
+ <string-array name="config_disabledDreamComponents" translatable="false">
+ </string-array>
<!-- Are we allowed to dream while not plugged in? -->
<bool name="config_dreamsEnabledOnBattery">false</bool>
@@ -4097,6 +4100,15 @@
-->
<string name="config_defaultAugmentedAutofillService" translatable="false"></string>
+ <!-- The package name list for the system's cloudsearch service.
+ This service returns cloudsearch results.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ If no service with the specified name exists on the device, cloudsearch will be disabled.
+ Example: "com.android.intelligence/.CloudSearchService"
+ config_defaultCloudSearchService is for the single provider case.
+ -->
+ <string name="config_defaultCloudSearchService" translatable="false"></string>
+
<!-- The package name for the system's translation service.
This service must be trusted, as it can be activated without explicit consent of the user.
If no service with the specified name exists on the device, translation wil be
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d374b74..771c072 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -131,15 +131,15 @@
corners. -->
<dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen>
+ <!-- Default paddings for content around the corners. -->
+ <dimen name="rounded_corner_content_padding">0dp</dimen>
+
<!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. -->
<dimen name="input_method_navigation_key_width">70dp</dimen>
<!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. -->
<dimen name="input_method_navigation_key_padding">0dp</dimen>
<!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME. -->
<dimen name="input_method_nav_content_padding">0px</dimen>
- <!-- Copied from SysUI's @dimen/rounded_corner_content_padding for the embedded nav bar in the
- IME. -->
- <dimen name="input_method_rounded_corner_content_padding">0px</dimen>
<!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the
IME. -->
<dimen name="input_method_nav_key_button_ripple_max_width">95dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index db348ed..bccd2b6 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -59,6 +59,12 @@
<item type="id" name="candidatesArea" />
<item type="id" name="inputArea" />
<item type="id" name="inputExtractEditText" />
+ <!-- View id for the action of text editor inside of an extracted text
+ {@link InputMethodService#onCreateExtractTextView IME extract view}. -->
+ <item type="id" name="inputExtractAction" />
+ <!-- View id for the accessories of text editor inside of an extracted text
+ {@link InputMethodService#onCreateExtractTextView IME extract view}. -->
+ <item type="id" name="inputExtractAccessories" />
<item type="id" name="selectAll" />
<item type="id" name="cut" />
<item type="id" name="copy" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 505fe59..2e96c65 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3259,6 +3259,7 @@
<public name="showBackground" />
<public name="inheritKeyStoreKeys" />
<public name="preferKeepClear" />
+ <public name="autoHandwritingEnabled" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
@@ -3267,6 +3268,8 @@
<public name="accessibilityActionSwipeUp" />
<public name="accessibilityActionSwipeDown" />
<public name="accessibilityActionShowSuggestions" />
+ <public name="inputExtractAction" />
+ <public name="inputExtractAccessories" />
</staging-public-group>
<staging-public-group type="style" first-id="0x01dd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59ad302..44ede49 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -463,10 +463,11 @@
<!-- SSL CA cert notification --> <skip />
<!-- Shows up when there is a user SSL CA Cert installed on the
device. Indicates to the user that SSL traffic can be intercepted. [CHAR LIMIT=NONE] -->
- <plurals name="ssl_ca_cert_warning">
- <item quantity="one">Certificate authority installed</item>
- <item quantity="other">Certificate authorities installed</item>
- </plurals>
+ <string name="ssl_ca_cert_warning">{count, plural,
+ =1 {Certificate authority installed}
+ other {Certificate authorities installed}
+ }
+ </string>
<!-- Content text for a notification. The Title of the notification is "ssl_ca_cert_warning".
This says that an unknown party is doing the monitoring. [CHAR LIMIT=100]-->
<string name="ssl_ca_cert_noti_by_unknown">By an unknown third party</string>
@@ -691,10 +692,11 @@
your device is unresponsive or too slow, or when you need all report sections.
Does not allow you to enter more details or take additional screenshots.</string>
<!-- Toast message informing user in how many seconds a bugreport screenshot will be taken -->
- <plurals name="bugreport_countdown">
- <item quantity="one">Taking screenshot for bug report in <xliff:g id="number">%d</xliff:g> second.</item>
- <item quantity="other">Taking screenshot for bug report in <xliff:g id="number">%d</xliff:g> seconds.</item>
- </plurals>
+ <string name="bugreport_countdown">{count, plural,
+ =1 {Taking screenshot for bug report in # second.}
+ other {Taking screenshot for bug report in # seconds.}
+ }
+ </string>
<!-- Format for build summary info [CHAR LIMIT=NONE] -->
<string name="bugreport_status" translatable="false">%s (%s)</string>
@@ -3066,10 +3068,10 @@
<string name="beforeOneMonthDurationPast">Before 1 month ago</string>
<!-- This is used to express that something occurred within the last X days (e.g., Last 7 days). -->
- <plurals name="last_num_days">
- <item quantity="one">Last <xliff:g id="count">%d</xliff:g> day</item>
- <item quantity="other">Last <xliff:g id="count">%d</xliff:g> days</item>
- </plurals>
+ <string name="last_num_days">{ count, plural,
+ =1 {Last # day}
+ other {Last # days}
+ }</string>
<!-- This is used to express that something has occurred within the last month -->
<string name="last_month">Last month</string>
@@ -3113,100 +3115,100 @@
<string name="now_string_shortest">now</string>
<!-- Phrase describing a time duration using minutes that is as short as possible, preferrably one character. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=6] -->
- <plurals name="duration_minutes_shortest">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g>m</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g>m</item>
- </plurals>
+ <string name="duration_minutes_shortest">
+ <xliff:g id="count">%d</xliff:g>m
+ </string>
<!-- Phrase describing a time duration using hours that is as short as possible, preferrably one character. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=6] -->
- <plurals name="duration_hours_shortest">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g>h</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g>h</item>
- </plurals>
+ <string name="duration_hours_shortest">
+ <xliff:g id="count">%d</xliff:g>h
+ </string>
<!-- Phrase describing a time duration using days that is as short as possible, preferrably one character. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=6] -->
- <plurals name="duration_days_shortest">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g>d</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g>d</item>
- </plurals>
+ <string name="duration_days_shortest">
+ <xliff:g id="count">%d</xliff:g>d
+ </string>
<!-- Phrase describing a time duration using years that is as short as possible, preferrably one character. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=6] -->
- <plurals name="duration_years_shortest">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g>y</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g>y</item>
- </plurals>
+ <string name="duration_years_shortest">
+ <xliff:g id="count">%d</xliff:g>y
+ </string>
<!-- Phrase describing a time duration using minutes that is as short as possible, preferrably one character. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
- <plurals name="duration_minutes_shortest_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g>m</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g>m</item>
- </plurals>
+ <string name="duration_minutes_shortest_future">
+ in <xliff:g id="count">%d</xliff:g>m
+ </string>
<!-- Phrase describing a time duration using hours that is as short as possible, preferrably one character. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
- <plurals name="duration_hours_shortest_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g>h</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g>h</item>
- </plurals>
+ <string name="duration_hours_shortest_future">
+ in <xliff:g id="count">%d</xliff:g>h
+ </string>
<!-- Phrase describing a time duration using days that is as short as possible, preferrably one character. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
- <plurals name="duration_days_shortest_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g>d</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g>d</item>
- </plurals>
+ <string name="duration_days_shortest_future">
+ in <xliff:g example="1" id="count">%d</xliff:g>d
+ </string>
<!-- Phrase describing a time duration using years that is as short as possible, preferrably one character. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
- <plurals name="duration_years_shortest_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g>y</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g>y</item>
- </plurals>
+ <string name="duration_years_shortest_future">
+ in <xliff:g id="count">%d</xliff:g>y
+ </string>
<!-- Phrase describing a relative time using minutes in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
- <plurals name="duration_minutes_relative">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g> minute ago</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g> minutes ago</item>
- </plurals>
+ <string name="duration_minutes_relative">{count, plural,
+ =1 {# minute ago}
+ other {# minutes ago}
+ }
+ </string>
<!-- Phrase describing a relative time using hours in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
- <plurals name="duration_hours_relative">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g> hour ago</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g> hours ago</item>
- </plurals>
+ <string name="duration_hours_relative">{count, plural,
+ =1 {# hour ago}
+ other {# hours ago}
+ }
+ </string>
<!-- Phrase describing a relative time using days in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
- <plurals name="duration_days_relative">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g> day ago</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g> days ago</item>
- </plurals>
+ <string name="duration_days_relative">{count, plural,
+ =1 {# day ago}
+ other {# days ago}
+ }
+ </string>
<!-- Phrase describing a relative time using years in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
- <plurals name="duration_years_relative">
- <item quantity="one"><xliff:g example="1" id="count">%d</xliff:g> year ago</item>
- <item quantity="other"><xliff:g example="2" id="count">%d</xliff:g> years ago</item>
- </plurals>
+ <string name="duration_years_relative">{count, plural,
+ =1 {# year ago}
+ other {# years ago}
+ }
+ </string>
<!-- Phrase describing a relative time using minutes that is not shown on the screen but used for accessibility. This version should be a future point in time. [CHAR LIMIT=NONE] -->
- <plurals name="duration_minutes_relative_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g> minute</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g> minutes</item>
- </plurals>
+ <string name="duration_minutes_relative_future">{count, plural,
+ =1 {# minute}
+ other {# minutes}
+ }
+ </string>
<!-- Phrase describing a relative time using hours that is not shown on the screen but used for accessibility. This version should be a future point in time. [CHAR LIMIT=NONE] -->
- <plurals name="duration_hours_relative_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g> hour</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g> hours</item>
- </plurals>
+ <string name="duration_hours_relative_future">{count, plural,
+ =1 {# hour}
+ other {# hours}
+ }
+ </string>
<!-- Phrase describing a relative time using days that is not shown on the screen but used for accessibility. This version should be a future point in time. [CHAR LIMIT=NONE] -->
- <plurals name="duration_days_relative_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g> day</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g> days</item>
- </plurals>
+ <string name="duration_days_relative_future">{count, plural,
+ =1 {# day}
+ other {# days}
+ }
+ </string>
<!-- Phrase describing a relative time using years that is not shown on the screen but used for accessibility. This version should be a future point in time. [CHAR LIMIT=NONE] -->
- <plurals name="duration_years_relative_future">
- <item quantity="one">in <xliff:g example="1" id="count">%d</xliff:g> year</item>
- <item quantity="other">in <xliff:g example="2" id="count">%d</xliff:g> years</item>
- </plurals>
+ <string name="duration_years_relative_future">{count, plural,
+ =1 {# year}
+ other {# years}
+ }
+ </string>
<!-- Title for error alert when a video cannot be played. it can be used by any app. -->
<string name="VideoView_error_title">Video problem</string>
@@ -4228,12 +4230,11 @@
<!-- Displayed on the Find dialog to display the index of the highlighted
match and total number of matches found in the current page. [CHAR LIMIT=NONE] -->
- <plurals name="matches_found">
- <!-- Case of one match -->
- <item quantity="one">1 match</item>
- <!-- Case of multiple total matches -->
- <item quantity="other"><xliff:g id="index" example="2">%d</xliff:g> of <xliff:g id="total" example="137">%d</xliff:g></item>
- </plurals>
+ <string name="matches_found">{ count, plural,
+ =1 {# match}
+ other {# of {total}}}
+ }
+ </string>
<!-- Label for the "Done" button on the far left of action mode toolbars. -->
<string name="action_mode_done">Done</string>
@@ -4595,11 +4596,6 @@
<string name="kg_wrong_password">Wrong Password</string>
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
- <!-- Countdown message shown after too many failed unlock attempts -->
- <plurals name="kg_too_many_failed_attempts_countdown">
- <item quantity="one">Try again in 1 second.</item>
- <item quantity="other">Try again in <xliff:g id="number">%d</xliff:g> seconds.</item>
- </plurals>
<!-- Instructions for using the pattern unlock screen -->
<string name="kg_pattern_instructions">Draw your pattern</string>
<!-- Instructions for using the SIM PIN unlock screen -->
@@ -5135,12 +5131,6 @@
<string name="restr_pin_error_doesnt_match">PINs don\'t match. Try again.</string>
<!-- PIN entry dialog error when PIN is too short [CHAR LIMIT=none] -->
<string name="restr_pin_error_too_short">PIN is too short. Must be at least 4 digits.</string>
- <!-- PIN entry dialog countdown message for next chance to enter the PIN [CHAR LIMIT=none] -->
- <!-- Phrase describing a time duration using seconds [CHAR LIMIT=none] -->
- <plurals name="restr_pin_countdown">
- <item quantity="one">Try again in 1 second</item>
- <item quantity="other">Try again in <xliff:g id="count">%d</xliff:g> seconds</item>
- </plurals>
<!-- PIN entry dialog tells the user to not enter a PIN for a while. [CHAR LIMIT=none] -->
<string name="restr_pin_try_later">Try again later</string>
@@ -5234,52 +5224,60 @@
<string name="data_saver_enable_button">Turn on</string>
<!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_minutes_summary">
- <item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- <item quantity="other">For %1$d minutes (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- </plurals>
+ <string name="zen_mode_duration_minutes_summary">{count, plural,
+ =1 {For one minute (until {formattedTime})}
+ other {For # minutes (until {formattedTime})}
+ }
+ </string>
<!-- Zen mode condition - summary: time duration in minutes (short version). [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_minutes_summary_short">
- <item quantity="one">For 1 min (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- <item quantity="other">For %1$d min (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- </plurals>
+ <string name="zen_mode_duration_minutes_summary_short">{count, plural,
+ =1 {For 1 min (until {formattedTime})}
+ other {For # min (until {formattedTime})}
+ }
+ </string>
<!-- Zen mode condition - summary: time duration in hours. [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_hours_summary">
- <item quantity="one">For 1 hour (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- <item quantity="other">For %1$d hours (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- </plurals>
+ <string name="zen_mode_duration_hours_summary">{count, plural,
+ =1 {For 1 hour (until {formattedTime})}
+ other {For # hours (until {formattedTime})}
+ }
+ </string>
<!-- Zen mode condition - summary: time duration in hours (short version). [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_hours_summary_short">
- <item quantity="one">For 1 hr (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- <item quantity="other">For %1$d hr (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
- </plurals>
+ <string name="zen_mode_duration_hours_summary_short">{count, plural,
+ =1 {For 1 hr (until {formattedTime})}
+ other {For # hr (until {formattedTime})}
+ }
+ </string>
<!-- Zen mode condition - line one: time duration in minutes. [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_minutes">
- <item quantity="one">For one minute</item>
- <item quantity="other">For %d minutes</item>
- </plurals>
+ <string name="zen_mode_duration_minutes">{count, plural,
+ =1 {For one minute}
+ other {For # minutes}
+ }
+ </string>
<!-- Zen mode condition - line one: time duration in minutes (short version). [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_minutes_short">
- <item quantity="one">For 1 min</item>
- <item quantity="other">For %d min</item>
- </plurals>
+ <string name="zen_mode_duration_minutes_short">{count, plural,
+ =1 {For 1 min}
+ other {For # min}
+ }
+ </string>
<!-- Zen mode condition - line one: time duration in hours. [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_hours">
- <item quantity="one">For 1 hour</item>
- <item quantity="other">For %d hours</item>
- </plurals>
+ <string name="zen_mode_duration_hours">{count, plural,
+ =1 {For 1 hour}
+ other {For # hours}
+ }
+ </string>
<!-- Zen mode condition - line one: time duration in hours (short version). [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_duration_hours_short">
- <item quantity="one">For 1 hr</item>
- <item quantity="other">For %d hr</item>
- </plurals>
+ <string name="zen_mode_duration_hours_short">{count, plural,
+ =1 {For 1 hr}
+ other {For # hr}
+ }
+ </string>
<!-- Zen mode condition - line two: ending time indicating the next day. [CHAR LIMIT=NONE] -->
<string name="zen_mode_until_next_day">Until <xliff:g id="formattedTime" example="Tue, 10 PM">%1$s</xliff:g></string>
@@ -5402,12 +5400,6 @@
<!-- Default notification text to be displayed in screening call notifications [CHAR LIMIT=40] -->
<string name="call_notification_screening_text">Screening an incoming call</string>
- <!-- Label describing the number of selected items [CHAR LIMIT=48] -->
- <plurals name="selected_count">
- <item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
- <item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
- </plurals>
-
<string name="default_notification_channel_label">Uncategorized</string>
<string name="importance_from_user">You set the importance of these notifications.</string>
@@ -5570,10 +5562,11 @@
<string name="autofill_picker_no_suggestions">No autofill suggestions</string>
<!-- Accessibility string to announce there are some autofill suggestions in the autofill picker. [CHAR LIMIT=NONE] -->
- <plurals name="autofill_picker_some_suggestions">
- <item quantity="one">One autofill suggestion</item>
- <item quantity="other"><xliff:g id="count" example="Two">%1$s</xliff:g> autofill suggestions</item>
- </plurals>
+ <string name="autofill_picker_some_suggestions">{count, plural,
+ =1 {One autofill suggestion}
+ other {# autofill suggestions}
+ }
+ </string>
<!-- Title for the autofill save dialog shown when the the contents of the activity can be saved
by an autofill service, but the service does not know what the activity represents [CHAR LIMIT=NONE] -->
@@ -5843,10 +5836,11 @@
<!-- String displayed when loading a user in the car [CHAR LIMIT=30] -->
<string name="car_loading_profile">Loading</string>
- <plurals name="file_count">
- <item quantity="one"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> file</item>
- <item quantity="other"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> files</item>
- </plurals>
+ <string name="file_count">{count, plural,
+ =1 {{file_name} + # file}
+ other {{file_name} + # files}
+ }
+ </string>
<!-- ChooserActivity - No direct share targets are available. [CHAR LIMIT=NONE] -->
<string name="chooser_no_direct_share_targets">No recommended people to share with</string>
@@ -6226,4 +6220,19 @@
<string name="ui_translation_accessibility_translated_text"><xliff:g id="message" example="Hello">%1$s</xliff:g> Translated.</string>
<!-- Accessibility message announced to notify the user when the system has finished translating the content displayed on the screen to a different language after the user requested translation. [CHAR LIMIT=NONE] -->
<string name="ui_translation_accessibility_translation_finished">Message translated from <xliff:g id="from_language" example="English">%1$s</xliff:g> to <xliff:g id="to_language" example="French">%2$s</xliff:g>.</string>
+
+ <!-- Title for the notification channel notifying user of abusive background apps. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_abusive_bg_apps">Background Activity</string>
+ <!-- Title of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
+ <string name="notification_title_abusive_bg_apps">Background Activity</string>
+ <!-- Content of notification indicating abusive background apps. [CHAR LIMIT=NONE] -->
+ <string name="notification_content_abusive_bg_apps">
+ <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background and draining battery. Tap to review.
+ </string>
+ <!-- Content of notification indicating long running foreground service. [CHAR LIMIT=NONE] -->
+ <string name="notification_content_long_running_fgs">
+ <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background for a long time. Tap to review.
+ </string>
+ <!-- Action label of notification for user to check background apps. [CHAR LIMIT=NONE] -->
+ <string name="notification_action_check_bg_apps">Check active apps</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ae2829..6cc93fc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1251,13 +1251,12 @@
<java-symbol type="string" name="conference_call" />
<java-symbol type="string" name="tooltip_popup_title" />
- <java-symbol type="plurals" name="bugreport_countdown" />
- <java-symbol type="plurals" name="file_count" />
- <java-symbol type="plurals" name="last_num_days" />
- <java-symbol type="plurals" name="matches_found" />
- <java-symbol type="plurals" name="restr_pin_countdown" />
+ <java-symbol type="string" name="bugreport_countdown" />
+ <java-symbol type="string" name="file_count" />
+ <java-symbol type="string" name="last_num_days" />
+ <java-symbol type="string" name="matches_found" />
<java-symbol type="plurals" name="pinpuk_attempts" />
- <java-symbol type="plurals" name="ssl_ca_cert_warning" />
+ <java-symbol type="string" name="ssl_ca_cert_warning" />
<java-symbol type="array" name="carrier_properties" />
<java-symbol type="array" name="config_sms_enabled_locking_shift_tables" />
@@ -2212,6 +2211,7 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
<java-symbol type="drawable" name="default_dream_preview" />
+ <java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2502,14 +2502,14 @@
<java-symbol type="string" name="zen_mode_forever" />
<java-symbol type="string" name="zen_mode_forever_dnd" />
<java-symbol type="string" name="zen_mode_rule_name_combination" />
- <java-symbol type="plurals" name="zen_mode_duration_minutes" />
- <java-symbol type="plurals" name="zen_mode_duration_hours" />
- <java-symbol type="plurals" name="zen_mode_duration_minutes_summary" />
- <java-symbol type="plurals" name="zen_mode_duration_hours_summary" />
- <java-symbol type="plurals" name="zen_mode_duration_minutes_short" />
- <java-symbol type="plurals" name="zen_mode_duration_hours_short" />
- <java-symbol type="plurals" name="zen_mode_duration_minutes_summary_short" />
- <java-symbol type="plurals" name="zen_mode_duration_hours_summary_short" />
+ <java-symbol type="string" name="zen_mode_duration_minutes" />
+ <java-symbol type="string" name="zen_mode_duration_hours" />
+ <java-symbol type="string" name="zen_mode_duration_minutes_summary" />
+ <java-symbol type="string" name="zen_mode_duration_hours_summary" />
+ <java-symbol type="string" name="zen_mode_duration_minutes_short" />
+ <java-symbol type="string" name="zen_mode_duration_hours_short" />
+ <java-symbol type="string" name="zen_mode_duration_minutes_summary_short" />
+ <java-symbol type="string" name="zen_mode_duration_hours_summary_short" />
<java-symbol type="string" name="zen_mode_until_next_day" />
<java-symbol type="string" name="zen_mode_until" />
<java-symbol type="string" name="zen_mode_feature_name" />
@@ -2941,7 +2941,6 @@
<java-symbol type="string" name="ext_media_status_missing" />
<java-symbol type="string" name="ext_media_unsupported_notification_message" />
<java-symbol type="string" name="ext_media_unsupported_notification_title" />
- <java-symbol type="plurals" name="selected_count" />
<java-symbol type="drawable" name="ic_dialog_alert_material" />
@@ -3112,23 +3111,23 @@
<java-symbol type="id" name="aerr_wait" />
- <java-symbol type="plurals" name="duration_minutes_shortest" />
- <java-symbol type="plurals" name="duration_hours_shortest" />
- <java-symbol type="plurals" name="duration_days_shortest" />
- <java-symbol type="plurals" name="duration_years_shortest" />
- <java-symbol type="plurals" name="duration_minutes_shortest_future" />
- <java-symbol type="plurals" name="duration_hours_shortest_future" />
- <java-symbol type="plurals" name="duration_days_shortest_future" />
- <java-symbol type="plurals" name="duration_years_shortest_future" />
+ <java-symbol type="string" name="duration_minutes_shortest" />
+ <java-symbol type="string" name="duration_hours_shortest" />
+ <java-symbol type="string" name="duration_days_shortest" />
+ <java-symbol type="string" name="duration_years_shortest" />
+ <java-symbol type="string" name="duration_minutes_shortest_future" />
+ <java-symbol type="string" name="duration_hours_shortest_future" />
+ <java-symbol type="string" name="duration_days_shortest_future" />
+ <java-symbol type="string" name="duration_years_shortest_future" />
- <java-symbol type="plurals" name="duration_minutes_relative" />
- <java-symbol type="plurals" name="duration_hours_relative" />
- <java-symbol type="plurals" name="duration_days_relative" />
- <java-symbol type="plurals" name="duration_years_relative" />
- <java-symbol type="plurals" name="duration_minutes_relative_future" />
- <java-symbol type="plurals" name="duration_hours_relative_future" />
- <java-symbol type="plurals" name="duration_days_relative_future" />
- <java-symbol type="plurals" name="duration_years_relative_future" />
+ <java-symbol type="string" name="duration_minutes_relative" />
+ <java-symbol type="string" name="duration_hours_relative" />
+ <java-symbol type="string" name="duration_days_relative" />
+ <java-symbol type="string" name="duration_years_relative" />
+ <java-symbol type="string" name="duration_minutes_relative_future" />
+ <java-symbol type="string" name="duration_hours_relative_future" />
+ <java-symbol type="string" name="duration_days_relative_future" />
+ <java-symbol type="string" name="duration_years_relative_future" />
<java-symbol type="string" name="now_string_shortest" />
@@ -3520,7 +3519,7 @@
<java-symbol type="id" name="autofill_save_yes" />
<java-symbol type="string" name="autofill_error_cannot_autofill" />
<java-symbol type="string" name="autofill_picker_no_suggestions" />
- <java-symbol type="plurals" name="autofill_picker_some_suggestions" />
+ <java-symbol type="string" name="autofill_picker_some_suggestions" />
<java-symbol type="string" name="autofill" />
<java-symbol type="string" name="autofill_picker_accessibility_title " />
<java-symbol type="string" name="autofill_update_title" />
@@ -3636,6 +3635,7 @@
<java-symbol type="string" name="notification_channel_network_status" />
<java-symbol type="string" name="notification_channel_network_alerts" />
<java-symbol type="string" name="notification_channel_network_available" />
+ <java-symbol type="string" name="config_defaultCloudSearchService" />
<java-symbol type="string" name="notification_channel_vpn" />
<java-symbol type="string" name="notification_channel_device_admin" />
<java-symbol type="string" name="notification_channel_alerts" />
@@ -4661,4 +4661,10 @@
<java-symbol type="string" name="config_deviceManagerUpdater" />
<java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
+
+ <java-symbol type="string" name="notification_channel_abusive_bg_apps"/>
+ <java-symbol type="string" name="notification_title_abusive_bg_apps"/>
+ <java-symbol type="string" name="notification_content_abusive_bg_apps"/>
+ <java-symbol type="string" name="notification_content_long_running_fgs"/>
+ <java-symbol type="string" name="notification_action_check_bg_apps"/>
</resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 205c517..c88e512 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -154,9 +154,9 @@
<!-- Israel: 4 digits, known premium codes listed -->
<shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
- <!-- Italy: 5 digits (premium=4xxxx), plus EU:
- http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
- <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503" standard="43\\d{3}" />
+ <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
+ https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
+ <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" />
<!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
<shortcode country="jp" pattern="\\d{1,5}" free="8083" />
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index e230a54..23b12cf 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -68,6 +68,7 @@
bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(),
proto.sessionDurationMillis);
assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage);
+ assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis);
assertEquals(3, proto.deviceBatteryConsumer.powerComponents.length); // Only 3 are non-empty
assertSameBatteryConsumer("For deviceBatteryConsumer",
@@ -215,6 +216,7 @@
/* includeProcessStats */true)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
+ .setDischargeDurationMs(1234)
.setStatsStartTimestamp(1000);
final UidBatteryConsumer.Builder uidBuilder = builder.getOrCreateUidBatteryConsumerBuilder(
batteryStatsUid0)
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 75da0bf..1467fed 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -29,7 +29,6 @@
import android.os.IBinder;
import android.os.PersistableBundle;
import android.util.MergedConfiguration;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -110,7 +109,6 @@
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
- private FixedRotationAdjustments mFixedRotationAdjustments;
private boolean mLaunchedFromBubble;
LaunchActivityItemBuilder setIntent(Intent intent) {
@@ -203,11 +201,6 @@
return this;
}
- LaunchActivityItemBuilder setFixedRotationAdjustments(FixedRotationAdjustments fra) {
- mFixedRotationAdjustments = fra;
- return this;
- }
-
LaunchActivityItemBuilder setLaunchedFromBubble(boolean launchedFromBubble) {
mLaunchedFromBubble = launchedFromBubble;
return this;
@@ -218,8 +211,8 @@
mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
- null /* activityClientController */, mFixedRotationAdjustments,
- mShareableActivityToken, mLaunchedFromBubble);
+ null /* activityClientController */, mShareableActivityToken,
+ mLaunchedFromBubble);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 60d48b2..b2c4274 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -56,9 +56,6 @@
import android.os.RemoteException;
import android.os.SharedMemory;
import android.platform.test.annotations.Presubmit;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
-import android.view.DisplayCutout;
-import android.view.Surface;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationSpec;
@@ -198,8 +195,6 @@
bundle.putParcelable("data", new ParcelableData(1));
PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
- FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
- Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT);
LaunchActivityItem item = new LaunchActivityItemBuilder()
.setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
@@ -207,8 +202,7 @@
.setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
.setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
.setPendingNewIntents(referrerIntentList()).setIsForward(true)
- .setAssistToken(new Binder()).setFixedRotationAdjustments(fixedRotationAdjustments)
- .setShareableActivityToken(new Binder())
+ .setAssistToken(new Binder()).setShareableActivityToken(new Binder())
.build();
writeAndPrepareForReading(item);
@@ -359,23 +353,6 @@
assertTrue(transaction.equals(result));
}
- @Test
- public void testFixedRotationAdjustments() {
- ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
- null /* activityToken */);
- transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
- new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080,
- DisplayCutout.NO_CUTOUT)));
-
- writeAndPrepareForReading(transaction);
-
- // Read from parcel and assert
- ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
-
- assertEquals(transaction.hashCode(), result.hashCode());
- assertTrue(transaction.equals(result));
- }
-
/** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
private void writeAndPrepareForReading(Parcelable parcelable) {
parcelable.writeToParcel(mParcel, 0 /* flags */);
diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
index 328429c..e690da2 100644
--- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
@@ -17,6 +17,7 @@
package android.content;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.activity.LocalProvider;
@@ -58,10 +59,12 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ final PackageManager pm = mContext.getPackageManager();
+ assumeTrue("device doesn't have the " + PackageManager.FEATURE_MANAGED_USERS + " feature",
+ pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS));
mUm = UserManager.get(mContext);
final UserInfo userInfo = createUser();
mCrossUserId = userInfo.id;
- final PackageManager pm = mContext.getPackageManager();
pm.installExistingPackageAsUser(mContext.getPackageName(), mCrossUserId);
unlockUser();
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 34a8bde..87c167c 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -281,37 +281,6 @@
}
@SmallTest
- public void testOverrideDisplayAdjustments() {
- final int originalOverrideDensity = 200;
- final int overrideDisplayDensity = 400;
- final Binder token = new Binder();
- final Configuration overrideConfig = new Configuration();
- overrideConfig.densityDpi = originalOverrideDensity;
- final Resources resources = mResourcesManager.createBaseTokenResources(
- token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* legacyOverlayDirs */,
- null /* overlayDirs */,null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig,
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* classLoader */,
- null /* loaders */);
-
- // Update the override.
- boolean handled = mResourcesManager.overrideTokenDisplayAdjustments(token,
- adjustments -> adjustments.getConfiguration().densityDpi = overrideDisplayDensity);
-
- assertTrue(handled);
- assertTrue(resources.hasOverrideDisplayAdjustments());
- assertEquals(overrideDisplayDensity,
- resources.getDisplayAdjustments().getConfiguration().densityDpi);
-
- // Clear the override.
- handled = mResourcesManager.overrideTokenDisplayAdjustments(token, null /* override */);
-
- assertTrue(handled);
- assertFalse(resources.hasOverrideDisplayAdjustments());
- assertEquals(originalOverrideDensity,
- resources.getDisplayAdjustments().getConfiguration().densityDpi);
- }
-
- @SmallTest
public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
Binder activity = new Binder();
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index 3cf1722..afbf8db 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -19,9 +19,6 @@
import static org.junit.Assert.assertEquals;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.util.DisplayMetrics;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -70,48 +67,4 @@
assertEquals(configuration, newAdjustments.getConfiguration());
}
-
- @Test
- public void testFixedRotationAdjustments() {
- final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
- final int realRotation = Surface.ROTATION_0;
- final int fixedRotation = Surface.ROTATION_90;
-
- final int appWidth = 1080;
- final int appHeight = 1920;
- mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments(
- fixedRotation, appWidth, appHeight, null /* cutout */));
-
- final int w = 1000;
- final int h = 2000;
- final Point size = new Point(w, h);
- mDisplayAdjustments.adjustSize(size, realRotation);
-
- assertEquals(fixedRotation, mDisplayAdjustments.getRotation(realRotation));
- assertEquals(new Point(h, w), size);
-
- final DisplayMetrics metrics = new DisplayMetrics();
- metrics.xdpi = metrics.noncompatXdpi = w;
- metrics.widthPixels = metrics.noncompatWidthPixels = w;
- metrics.ydpi = metrics.noncompatYdpi = h;
- metrics.heightPixels = metrics.noncompatHeightPixels = h;
-
- final DisplayMetrics flippedMetrics = new DisplayMetrics();
- // The physical dpi should not be adjusted.
- flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w;
- flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
- flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h;
- flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
-
- mDisplayAdjustments.adjustMetrics(metrics, realRotation);
-
- assertEquals(flippedMetrics, metrics);
-
- mDisplayAdjustments.adjustGlobalAppMetrics(metrics);
-
- assertEquals(appWidth, metrics.widthPixels);
- assertEquals(appWidth, metrics.noncompatWidthPixels);
- assertEquals(appHeight, metrics.heightPixels);
- assertEquals(appHeight, metrics.noncompatHeightPixels);
- }
}
diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
index 5ea9199..632a1a9 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
@@ -59,34 +59,20 @@
private HandwritingInitiator mHandwritingInitiator;
private View mTestView;
+ private Context mContext;
@Before
public void setup() {
final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
- Context context = mInstrumentation.getTargetContext();
+ mContext = mInstrumentation.getTargetContext();
ViewConfiguration viewConfiguration = mock(ViewConfiguration.class);
when(viewConfiguration.getScaledTouchSlop()).thenReturn(TOUCH_SLOP);
- InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class);
+ InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class);
mHandwritingInitiator =
spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
- // mock a parent so that HandwritingInitiator can get
- ViewGroup parent = new ViewGroup(context) {
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // We don't layout this view.
- }
- @Override
- public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
- r.set(sHwArea);
- return true;
- }
- };
-
- mTestView = mock(View.class);
- when(mTestView.isAttachedToWindow()).thenReturn(true);
- parent.addView(mTestView);
+ mTestView = createMockView(sHwArea, true);
}
@Test
@@ -203,14 +189,42 @@
}
@Test
- public void onInputConnectionCreated_inputConnectionCreated() {
+ public void autoHandwriting_whenDisabled_wontStartHW() {
+ View mockView = createMockView(sHwArea, false);
+ mHandwritingInitiator.onInputConnectionCreated(mockView);
+ 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 + TOUCH_SLOP * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+ }
+
+ @Test
+ public void onInputConnectionCreated() {
mHandwritingInitiator.onInputConnectionCreated(mTestView);
assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
}
@Test
- public void onInputConnectionCreated_inputConnectionClosed() {
+ public void onInputConnectionCreated_whenAutoHandwritingIsDisabled() {
+ View view = new View(mContext);
+ view.setAutoHandwritingEnabled(false);
+ assertThat(view.isAutoHandwritingEnabled()).isFalse();
+ mHandwritingInitiator.onInputConnectionCreated(view);
+
+ assertThat(mHandwritingInitiator.mConnectedView).isNull();
+ }
+
+ @Test
+ public void onInputConnectionClosed() {
mHandwritingInitiator.onInputConnectionCreated(mTestView);
mHandwritingInitiator.onInputConnectionClosed(mTestView);
@@ -218,6 +232,16 @@
}
@Test
+ public void onInputConnectionClosed_whenAutoHandwritingIsDisabled() {
+ View view = new View(mContext);
+ view.setAutoHandwritingEnabled(false);
+ mHandwritingInitiator.onInputConnectionCreated(view);
+ mHandwritingInitiator.onInputConnectionClosed(view);
+
+ assertThat(mHandwritingInitiator.mConnectedView).isNull();
+ }
+
+ @Test
public void onInputConnectionCreated_inputConnectionRestarted() {
// When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
// called before View#onInputConnectionClosedInternal. As a result, we need to handle the
@@ -243,4 +267,25 @@
1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */);
}
+
+ private View createMockView(Rect viewBound, boolean autoHandwritingEnabled) {
+ // mock a parent so that HandwritingInitiator can get
+ ViewGroup parent = new ViewGroup(mContext) {
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // We don't layout this view.
+ }
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ r.set(viewBound);
+ return true;
+ }
+ };
+
+ View mockView = mock(View.class);
+ when(mockView.isAttachedToWindow()).thenReturn(true);
+ when(mockView.isAutoHandwritingEnabled()).thenReturn(autoHandwritingEnabled);
+ parent.addView(mockView);
+ return mockView;
+ }
}
diff --git a/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
new file mode 100644
index 0000000..4731e81
--- /dev/null
+++ b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlViewHostInsetsTest {
+ SurfaceControlViewHost mSurfaceControlViewHost;
+ private boolean mStatusBarIsVisible = false;
+ private Insets mStatusBarInsets;
+ private Instrumentation mInstrumentation;
+
+ private void createViewHierarchy() {
+ Context context = mInstrumentation.getTargetContext();
+
+ View v = new View(context);
+ v.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets w) {
+ mStatusBarIsVisible = w.isVisible(WindowInsets.Type.statusBars());
+ mStatusBarInsets = w.getInsets(WindowInsets.Type.statusBars());
+ return w;
+ }
+ });
+ mSurfaceControlViewHost = new SurfaceControlViewHost(context,
+ context.getDisplayNoVerify(), new Binder());
+ mSurfaceControlViewHost.setView(v, 100, 100);
+ }
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mInstrumentation.runOnMainSync(() -> { createViewHierarchy(); });
+ mInstrumentation.waitForIdleSync();
+ }
+
+ private InsetsState statusBarState(boolean visible) {
+ final InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, 1000));
+ insetsState.getSource(ITYPE_STATUS_BAR).setVisible(visible);
+ insetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+ return insetsState;
+ }
+
+ private InsetsState statusBarVisibleState() {
+ return statusBarState(true);
+ }
+
+ private void sendInsetsSync(InsetsState s, Rect f) {
+ try {
+ mSurfaceControlViewHost.getSurfacePackage().getRemoteInterface()
+ .onInsetsChanged(s, f);
+ } catch (Exception e) {
+ }
+ mInstrumentation.waitForIdleSync();
+ }
+
+ @Test
+ public void sendInsetsToSurfaceControlViewHost() {
+ final InsetsState insetsState = statusBarVisibleState();
+ sendInsetsSync(insetsState, new Rect(0, 0, 100, 100));
+ assertTrue(mStatusBarIsVisible);
+
+ final InsetsState insetsState2 = statusBarState(false);
+ sendInsetsSync(insetsState2, new Rect(0, 0, 100, 100));
+ assertFalse(mStatusBarIsVisible);
+ }
+
+ @Test
+ public void insetsAreRelativeToFrame() {
+ final InsetsState insetsState = statusBarVisibleState();
+ sendInsetsSync(insetsState, new Rect(0, 0, 100, 100));
+
+ assertTrue(mStatusBarIsVisible);
+ assertEquals(10, mStatusBarInsets.top);
+
+ sendInsetsSync(insetsState, new Rect(0, 5, 100, 100));
+ assertEquals(5, mStatusBarInsets.top);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
index 6457e3f..96d6a7e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
@@ -54,6 +54,7 @@
/* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
2_000_000, 2_000_000, 2_000_000);
+ mStatsRule.setTime(5_000_000, 5_000_000);
BatteryChargeCalculator calculator = new BatteryChargeCalculator();
BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator);
@@ -64,6 +65,8 @@
.isWithin(PRECISION).of(360.0);
assertThat(batteryUsageStats.getDischargedPowerRange().getUpper())
.isWithin(PRECISION).of(400.0);
+ // 5_000_000 (current time) - 1_000_000 (started discharging)
+ assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(4_000_000);
assertThat(batteryUsageStats.getBatteryTimeRemainingMs()).isEqualTo(8_000_000);
assertThat(batteryUsageStats.getChargeTimeRemainingMs()).isEqualTo(-1);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 9b3876f..354b937 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -50,19 +50,75 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
public class BatteryUsageStatsProviderTest {
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
private static final long MINUTE_IN_MS = 60 * 1000;
+ private static final double PRECISION = 0.00001;
private final File mHistoryDir =
TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
@Rule
public final BatteryUsageStatsRule mStatsRule =
new BatteryUsageStatsRule(12345, mHistoryDir)
- .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0);
+ .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
+ .setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
@Test
public void test_getBatteryUsageStats() {
+ BatteryStatsImpl batteryStats = prepareBatteryStats();
+
+ Context context = InstrumentationRegistry.getContext();
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+
+ final BatteryUsageStats batteryUsageStats =
+ provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
+
+ final List<UidBatteryConsumer> uidBatteryConsumers =
+ batteryUsageStats.getUidBatteryConsumers();
+ final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
+ assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
+ .isEqualTo(60 * MINUTE_IN_MS);
+ assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
+ .isEqualTo(10 * MINUTE_IN_MS);
+ assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isWithin(PRECISION).of(2.0);
+ assertThat(
+ uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isWithin(PRECISION).of(0.4);
+
+ assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
+ assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+ }
+
+ @Test
+ public void test_selectPowerComponents() {
+ BatteryStatsImpl batteryStats = prepareBatteryStats();
+
+ Context context = InstrumentationRegistry.getContext();
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
+
+ final BatteryUsageStats batteryUsageStats =
+ provider.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder()
+ .includePowerComponents(
+ new int[]{BatteryConsumer.POWER_COMPONENT_AUDIO})
+ .build()
+ );
+
+ final List<UidBatteryConsumer> uidBatteryConsumers =
+ batteryUsageStats.getUidBatteryConsumers();
+ final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
+ assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isWithin(PRECISION).of(2.0);
+
+ // FLASHLIGHT power estimation not requested, so the returned value is 0
+ assertThat(
+ uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(0);
+ }
+
+ private BatteryStatsImpl prepareBatteryStats() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.noteActivityResumedLocked(APP_UID,
@@ -82,24 +138,14 @@
batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
+ batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
+
+ batteryStats.noteAudioOnLocked(APP_UID, 10000, 10000);
+ batteryStats.noteAudioOffLocked(APP_UID, 20000, 20000);
+
mStatsRule.setCurrentTime(54321);
-
- Context context = InstrumentationRegistry.getContext();
- BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats);
-
- final BatteryUsageStats batteryUsageStats =
- provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT);
-
- final List<UidBatteryConsumer> uidBatteryConsumers =
- batteryUsageStats.getUidBatteryConsumers();
- final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
- assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
- .isEqualTo(60 * MINUTE_IN_MS);
- assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
- .isEqualTo(10 * MINUTE_IN_MS);
-
- assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(12345);
- assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(54321);
+ return batteryStats;
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index b3056e2..4f29863 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -212,8 +212,8 @@
}
for (PowerCalculator calculator : calculators) {
- calculator.calculate(builder, mBatteryStats, mMockClock.realtime, mMockClock.uptime,
- query);
+ calculator.calculate(builder, mBatteryStats, mMockClock.realtime * 1000,
+ mMockClock.uptime * 1000, query);
}
mBatteryUsageStats = builder.build();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
index 51f20f3..21f6e7c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java
@@ -44,6 +44,7 @@
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
public class BatteryUsageStatsStoreTest {
private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
@@ -76,8 +77,13 @@
@Test
public void testStoreSnapshot() {
mMockClock.currentTime = 1_600_000;
+ mMockClock.realtime = 1000;
+ mMockClock.uptime = 1000;
prepareBatteryStats();
+
+ mMockClock.realtime = 1_000_000;
+ mMockClock.uptime = 1_000_000;
mBatteryStats.resetAllStatsCmdLocked();
final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
@@ -90,6 +96,7 @@
assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
+ assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
assertThat(batteryUsageStats.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
.isEqualTo(600); // (3_600_000 - 3_000_000) / 1000
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 8cc4c34..5adc9bd 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -183,7 +183,7 @@
.add(stats2)
.build();
- assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1000, 5000, 5000);
+ assertBatteryUsageStats(sum, 42345, 50, 2234, 4345, 1234, 1000, 5000, 5000);
final List<UidBatteryConsumer> uidBatteryConsumers =
sum.getUidBatteryConsumers();
@@ -259,6 +259,7 @@
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
+ .setDischargeDurationMs(1234)
.setStatsStartTimestamp(1000)
.setStatsEndTimestamp(3000);
@@ -420,7 +421,7 @@
public void assertBatteryUsageStats1(BatteryUsageStats batteryUsageStats,
boolean includesUserBatteryConsumers) {
- assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1000, 3000, 2000);
+ assertBatteryUsageStats(batteryUsageStats, 30000, 20, 1000, 2000, 1234, 1000, 3000, 2000);
final List<UidBatteryConsumer> uidBatteryConsumers =
batteryUsageStats.getUidBatteryConsumers();
@@ -463,13 +464,15 @@
private void assertBatteryUsageStats(BatteryUsageStats batteryUsageStats, int consumedPower,
int dischargePercentage, int dischagePowerLower, int dischargePowerUpper,
- int statsStartTimestamp, int statsEndTimestamp, int statsDuration) {
+ int dischargeDuration, int statsStartTimestamp, int statsEndTimestamp,
+ int statsDuration) {
assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(consumedPower);
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(dischargePercentage);
assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(
dischagePowerLower);
assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(
dischargePowerUpper);
+ assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(dischargeDuration);
assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(statsStartTimestamp);
assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(statsEndTimestamp);
assertThat(batteryUsageStats.getStatsDuration()).isEqualTo(statsDuration);
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 67b1e51..2b28031 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -39,7 +39,7 @@
@Test
public void testTimerBasedModel() {
- mStatsRule.setTime(3_000_000, 2_000_000);
+ mStatsRule.setTime(3_000, 2_000);
IdlePowerCalculator calculator = new IdlePowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index ce2f764..c20293b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -102,7 +102,7 @@
stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
mNetworkStatsManager);
- mStatsRule.setTime(12_000_000, 12_000_000);
+ mStatsRule.setTime(12_000, 12_000);
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
@@ -248,7 +248,7 @@
new int[]{100, 200, 300, 400, 500}, 600);
stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000, mNetworkStatsManager);
- mStatsRule.setTime(12_000_000, 12_000_000);
+ mStatsRule.setTime(12_000, 12_000);
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
index aae69d7..aec4f52 100644
--- a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
@@ -126,6 +126,13 @@
}
private static class FakeAudioPowerCalculator extends PowerCalculator {
+
+ @Override
+ public boolean isPowerComponentSupported(
+ @BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
+ }
+
@Override
protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
@@ -135,6 +142,13 @@
}
private static class FakeVideoPowerCalculator extends PowerCalculator {
+
+ @Override
+ public boolean isPowerComponentSupported(
+ @BatteryConsumer.PowerComponent int powerComponent) {
+ return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO;
+ }
+
@Override
protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
index a7f4fb3..f3456af 100644
--- a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -54,7 +54,7 @@
batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
- mStatsRule.setTime(10_000_000, 6_000_000);
+ mStatsRule.setTime(10_000, 6_000);
WakelockPowerCalculator calculator =
new WakelockPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 269d842..f8db069 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -297,8 +297,7 @@
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
- mThread /* client */, null /* asssitToken */,
- null /* fixedRotationAdjustments */, null /* shareableActivityToken */,
+ mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
false /* launchedFromBubble */);
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index f8dd153..0c939ec 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -27,6 +27,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -34,7 +35,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,8 +49,6 @@
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
-import java.util.function.Consumer;
-
/**
* Tests for {@link Display}.
*
@@ -96,14 +94,12 @@
// Ensure no adjustments are set before each test.
mApplicationContext = ApplicationProvider.getApplicationContext();
- DisplayAdjustments displayAdjustments =
- mApplicationContext.getResources().getDisplayAdjustments();
- displayAdjustments.setFixedRotationAdjustments(null);
- mApplicationContext.getResources().overrideDisplayAdjustments(null);
mApplicationContext.getResources().getConfiguration().windowConfiguration.setAppBounds(
null);
mApplicationContext.getResources().getConfiguration().windowConfiguration.setMaxBounds(
null);
+ mApplicationContext.getResources().getConfiguration().windowConfiguration
+ .setDisplayRotation(WindowConfiguration.ROTATION_UNDEFINED);
mDisplayInfo.rotation = ROTATION_0;
mDisplayManagerGlobal = mock(DisplayManagerGlobal.class);
@@ -151,41 +147,11 @@
}
@Test
- public void testGetRotation_displayAdjustmentsWithoutOverride_rotationNotAdjusted() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated, but no override is set.
- DisplayAdjustments displayAdjustments = DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
- final FixedRotationAdjustments fixedRotationAdjustments =
- new FixedRotationAdjustments(ROTATION_90, APP_WIDTH, APP_HEIGHT,
- DisplayCutout.NO_CUTOUT);
- displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
- // GIVEN display is constructed with display adjustments.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- displayAdjustments);
- // THEN rotation is not adjusted since no override was set.
- assertThat(display.getRotation()).isEqualTo(ROTATION_0);
- }
-
- @Test
- public void testGetRotation_resourcesWithoutOverride_rotationNotAdjusted() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated, but no override is set.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN rotation is not adjusted since no override is set.
- assertThat(display.getRotation()).isEqualTo(ROTATION_0);
- }
-
- @Test
public void testGetRotation_resourcesWithOverrideDisplayAdjustments_rotationAdjusted() {
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -234,37 +200,11 @@
}
@Test
- public void testGetRealSize_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is rotated.
- setDisplayInfoLandscape(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real size matches display orientation.
- verifyRealSizeIsLandscape(display);
- }
-
- @Test
- public void testGetRealSize_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real size matches display orientation.
- verifyRealSizeIsPortrait(display);
- }
-
- @Test
public void testGetRealSize_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
// GIVEN display is rotated.
setDisplayInfoLandscape(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_0);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -277,7 +217,7 @@
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -380,37 +320,11 @@
}
@Test
- public void testGetRealMetrics_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is rotated.
- setDisplayInfoLandscape(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real metrics matches display orientation.
- verifyRealMetricsIsLandscape(display);
- }
-
- @Test
- public void testGetRealMetrics_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real metrics matches display orientation.
- verifyRealMetricsIsPortrait(display);
- }
-
- @Test
public void testGetRealMetrics_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
// GIVEN display is rotated.
setDisplayInfoLandscape(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated with an override.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_0);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -423,7 +337,7 @@
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -569,27 +483,8 @@
assertThat(metrics.heightPixels).isEqualTo(bounds.height());
}
- private static FixedRotationAdjustments setOverrideFixedRotationAdjustments(
- Resources resources, @Surface.Rotation int rotation) {
- FixedRotationAdjustments fixedRotationAdjustments =
- setFixedRotationAdjustments(resources, rotation);
- resources.overrideDisplayAdjustments(
- buildOverrideRotationAdjustments(fixedRotationAdjustments));
- return fixedRotationAdjustments;
- }
-
- private static FixedRotationAdjustments setFixedRotationAdjustments(Resources resources,
+ private static void setLocalDisplayInConfig(Resources resources,
@Surface.Rotation int rotation) {
- final FixedRotationAdjustments fixedRotationAdjustments =
- new FixedRotationAdjustments(rotation, APP_WIDTH, APP_HEIGHT,
- DisplayCutout.NO_CUTOUT);
- resources.getDisplayAdjustments().setFixedRotationAdjustments(fixedRotationAdjustments);
- return fixedRotationAdjustments;
- }
-
- private static Consumer<DisplayAdjustments> buildOverrideRotationAdjustments(
- FixedRotationAdjustments fixedRotationAdjustments) {
- return consumedDisplayAdjustments
- -> consumedDisplayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
+ resources.getConfiguration().windowConfiguration.setDisplayRotation(rotation);
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0e06fac..e68b1ac 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -391,6 +391,7 @@
<permission name="android.permission.STATUS_BAR_SERVICE"/>
<permission name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"/>
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
+ <permission name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"/>
<permission name="android.permission.SET_WALLPAPER" />
<permission name="android.permission.SET_WALLPAPER_COMPONENT" />
<permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
@@ -435,6 +436,7 @@
<permission name="android.permission.MANAGE_COMPANION_DEVICES" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+ <permission name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<!-- Permission required for testing registering pull atom callbacks. -->
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
@@ -471,6 +473,7 @@
<!-- Permission needed for CTS test - WifiManagerTest -->
<permission name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
<permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG" />
<!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<!-- Permission required for CTS test - MusicRecognitionManagerTest -->
diff --git a/libs/WindowManager/Shell/res/values-af/strings_tv.xml b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
index 3edb8e9..1bfe128 100644
--- a/libs/WindowManager/Shell/res/values-af/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Titellose program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Maak PIP toe"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volskerm"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Skuif PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index b1c6542..456b4b8 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPን ዝጋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"ፒአይፒ ውሰድ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
index dfc5053..2546fe9 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ليس هناك عنوان للبرنامج)"</string>
<string name="pip_close" msgid="9135220303720555525">"إغلاق PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ملء الشاشة"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"نقل نافذة داخل النافذة (PIP)"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings_tv.xml b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
index 352bde5..d17c1f3 100644
--- a/libs/WindowManager/Shell/res/values-as/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিৰোনামবিহীন কাৰ্যক্ৰম)"</string>
<string name="pip_close" msgid="9135220303720555525">"পিপ বন্ধ কৰক"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"সম্পূৰ্ণ স্ক্ৰীন"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"পিপ স্থানান্তৰ কৰক"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings_tv.xml b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
index 9b46d5f..a5c4792 100644
--- a/libs/WindowManager/Shell/res/values-az/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıqsız proqram)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP bağlayın"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP tətbiq edin"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
index 790a6d47..b4d9bd1 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ceo ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Premesti sliku u slici"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings_tv.xml b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
index c4df7fc..514d06b 100644
--- a/libs/WindowManager/Shell/res/values-be/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Праграма без назвы)"</string>
<string name="pip_close" msgid="9135220303720555525">"Закрыць PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Поўнаэкранны рэжым"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Перамясціць PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
index cbb00ae..19f83e7 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без заглавие)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затваряне на PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цял екран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"„Картина в картина“: Преместв."</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
index f24c92a..5f90eeb 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(শিরোনামহীন প্রোগ্রাম)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP বন্ধ করুন"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"পূর্ণ স্ক্রিন"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP সরান"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
index 80bac2a..3f2adf3 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zatvori sliku u slici"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Pokreni sliku u slici"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index 66cd93a..db750c4 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tanca PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mou pantalla en pantalla"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
index 500050b..cef0b99 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Bez názvu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Ukončit obraz v obraze (PIP)"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Přesunout PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings_tv.xml b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
index 896895b..2330530 100644
--- a/libs/WindowManager/Shell/res/values-da/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uden titel)"</string>
<string name="pip_close" msgid="9135220303720555525">"Luk integreret billede"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fuld skærm"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Flyt PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings_tv.xml b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
index 7809efe..8da9110 100644
--- a/libs/WindowManager/Shell/res/values-de/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Kein Sendungsname gefunden)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP schließen"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Vollbild"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"BiB verschieben"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings_tv.xml b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
index 088bcc3..df35113 100644
--- a/libs/WindowManager/Shell/res/values-el/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Δεν υπάρχει τίτλος προγράμματος)"</string>
<string name="pip_close" msgid="9135220303720555525">"Κλείσιμο PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Πλήρης οθόνη"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Μετακίνηση PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
index 7900fdc..1fb3191 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Close PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Move PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
index 3be850a..1beb0b5 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sin título de programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 7eba361..d042b43 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sin título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Cerrar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover imagen en imagen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings_tv.xml b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
index ca6e669..3da16db 100644
--- a/libs/WindowManager/Shell/res/values-et/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programmi pealkiri puudub)"</string>
<string name="pip_close" msgid="9135220303720555525">"Sule PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Täisekraan"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Teisalda PIP-režiimi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
index 3f47e95..e4b57ba 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa izengabea)"</string>
<string name="pip_close" msgid="9135220303720555525">"Itxi PIPa"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantaila osoa"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mugitu pantaila txiki gainjarria"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index cc5cf64..aaab34f 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string>
<string name="pip_close" msgid="9135220303720555525">"بستن PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"انتقال PIP (تصویر در تصویر)"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
index b779886..21c6463 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nimetön)"</string>
<string name="pip_close" msgid="9135220303720555525">"Sulje PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Koko näyttö"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Siirrä PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
index 1798c7d..f4baaad 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Aucun programme de titre)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fermer mode IDI"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Déplacer l\'image incrustée"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
index b039934..6ad8174 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programme sans titre)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fermer mode PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Plein écran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Déplacer le PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
index 0d91eba..dcb8709 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sen título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Pechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover pantalla superposta"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
index a748df3..ed815ca 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(કોઈ ટાઇટલ પ્રોગ્રામ નથી)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP બંધ કરો"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"પૂર્ણ સ્ક્રીન"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP ખસેડો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
index 040072b..8bcc631 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(कोई शीर्षक कार्यक्रम नहीं)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP बंद करें"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फ़ुल स्क्रीन"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"पीआईपी को दूसरी जगह लेकर जाएं"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
index 20081e4..49b7ae0 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zatvori PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Cijeli zaslon"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Premjesti PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
index c78146d..484db0c 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Cím nélküli program)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP bezárása"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Teljes képernyő"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP áthelyezése"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
index 55d5bd7..e447ffc 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Առանց վերնագրի ծրագիր)"</string>
<string name="pip_close" msgid="9135220303720555525">"Փակել PIP-ն"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Լիէկրան"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Տեղափոխել PIP-ը"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings_tv.xml b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
index 6401852..b631705 100644
--- a/libs/WindowManager/Shell/res/values-in/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tanpa judul)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Layar penuh"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Pindahkan PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings_tv.xml b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
index fa36829..119ecf0 100644
--- a/libs/WindowManager/Shell/res/values-is/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Efni án titils)"</string>
<string name="pip_close" msgid="9135220303720555525">"Loka mynd í mynd"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Allur skjárinn"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Færa innfellda mynd"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings_tv.xml b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
index f6e91be..92f015c 100644
--- a/libs/WindowManager/Shell/res/values-it/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma senza titolo)"</string>
<string name="pip_close" msgid="9135220303720555525">"Chiudi PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Schermo intero"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Sposta PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
index 356e8d5..d09b850 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(תוכנית ללא כותרת)"</string>
<string name="pip_close" msgid="9135220303720555525">"סגירת PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"מסך מלא"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"העברת תמונה בתוך תמונה (PIP)"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
index 07684d3..d6399e5 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無題の番組)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP を閉じる"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全画面表示"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP を移動"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
index 043e5eb..8d7bee8 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(პროგრამის სათაურის გარეშე)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP-ის დახურვა"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"სრულ ეკრანზე"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP გადატანა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
index 7943797..05bdcc7 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Атаусыз бағдарлама)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP жабу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толық экран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP клипін жылжыту"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings_tv.xml b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
index 2e56254..e831516 100644
--- a/libs/WindowManager/Shell/res/values-km/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(កម្មវិធីគ្មានចំណងជើង)"</string>
<string name="pip_close" msgid="9135220303720555525">"បិទ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ពេញអេក្រង់"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"ផ្លាស់ទី PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
index 6c8880d..305ef66 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ಮುಚ್ಚಿ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ಪೂರ್ಣ ಪರದೆ"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP ಅನ್ನು ಸರಿಸಿ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
index 35b1b19..76b0adf 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(제목 없는 프로그램)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP 닫기"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"전체화면"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP 이동"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
index 72d70f0..57b955a 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Аталышы жок программа)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP\'ти жабуу"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Толук экран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP\'ти жылдыруу"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
index 3604726..cbea84e 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ໂປຣແກຣມບໍ່ມີຊື່)"</string>
<string name="pip_close" msgid="9135220303720555525">"ປິດ PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ເຕັມໜ້າຈໍ"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"ຍ້າຍ PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
index fa5a4c4..81716a6 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa be pavadinimo)"</string>
<string name="pip_close" msgid="9135220303720555525">"Uždaryti PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Visas ekranas"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Perkelti PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
index cafd43a..5295cd2 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programma bez nosaukuma)"</string>
<string name="pip_close" msgid="9135220303720555525">"Aizvērt PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pilnekrāna režīms"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Pārvietot attēlu attēlā"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
index b927b56..fa48a6c 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без наслов)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цел екран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Премести PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
index aef059f..5333757 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(പേരില്ലാത്ത പ്രോഗ്രാം)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP അടയ്ക്കുക"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"പൂര്ണ്ണ സ്ക്രീന്"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP നീക്കുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
index 7dfec68..ca1d27f 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Гарчиггүй хөтөлбөр)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP-г хаах"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Бүтэн дэлгэц"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP-г зөөх"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
index 447cb7d..212bd21 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षक नसलेला कार्यक्रम)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP बंद करा"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रीन"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP हलवा"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
index 3a5584d..ce29126 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program tiada tajuk)"</string>
<string name="pip_close" msgid="9135220303720555525">"Tutup PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrin penuh"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Alihkan PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings_tv.xml b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
index 84ec0e5..4847742 100644
--- a/libs/WindowManager/Shell/res/values-my/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ခေါင်းစဉ်မဲ့ အစီအစဉ်)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ကိုပိတ်ပါ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"မျက်နှာပြင် အပြည့်"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP ရွှေ့ရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
index 78ec6db..7cef11c 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program uten tittel)"</string>
<string name="pip_close" msgid="9135220303720555525">"Lukk PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Fullskjerm"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Flytt BIB"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
index 4458a14..684d114 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(शीर्षकविहीन कार्यक्रम)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP लाई बन्द गर्नुहोस्"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"फुल स्क्रिन"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP सार्नुहोस्"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
index d21515d..8562517 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Naamloos programma)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP sluiten"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Volledig scherm"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"SIS verplaatsen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings_tv.xml b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
index a679350..f8bc016 100644
--- a/libs/WindowManager/Shell/res/values-or/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(କୌଣସି ଟାଇଟଲ୍ ପ୍ରୋଗ୍ରାମ୍ ନାହିଁ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIPକୁ ମୁଭ କରନ୍ତୁ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
index a0ff4f3..1667e5f 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ਸਿਰਲੇਖ-ਰਹਿਤ ਪ੍ਰੋਗਰਾਮ)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP ਬੰਦ ਕਰੋ"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP ਨੂੰ ਲਿਜਾਓ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
index 6320893..28bf66a 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez tytułu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zamknij PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pełny ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Przenieś PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
index fef9d47..27626b8 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
index 461571f..a2010ce 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Sem título do programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecrã inteiro"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover Ecrã no ecrã"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
index fef9d47..27626b8 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(programa sem título)"</string>
<string name="pip_close" msgid="9135220303720555525">"Fechar PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tela cheia"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mover picture-in-picture"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
index 80bf151..18e29a6 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program fără titlu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Închideți PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ecran complet"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Mutați fereastra PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
index de5348a..d119240 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Без названия)"</string>
<string name="pip_close" msgid="9135220303720555525">"\"Кадр в кадре\" – выйти"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Во весь экран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Переместить PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings_tv.xml b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
index 0470040..86769b6 100644
--- a/libs/WindowManager/Shell/res/values-si/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(මාතෘකාවක් නැති වැඩසටහන)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP වසන්න"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"සම්පූර්ණ තිරය"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP ගෙන යන්න"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
index 41a432c..6f6ccb7 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program bez názvu)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zavrieť režim PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celá obrazovka"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Presunúť obraz v obraze"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
index de5605f..837794a 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program brez naslova)"</string>
<string name="pip_close" msgid="9135220303720555525">"Zapri način PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Celozaslonsko"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Premakni sliko v sliki"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
index 08a6409..107870d0 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Program pa titull)"</string>
<string name="pip_close" msgid="9135220303720555525">"Mbyll PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Ekrani i plotë"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Zhvendos PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
index f932928..ee5690b 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програм без наслова)"</string>
<string name="pip_close" msgid="9135220303720555525">"Затвори PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Цео екран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Премести слику у слици"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
index 1428fdb..7355adf 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Namnlöst program)"</string>
<string name="pip_close" msgid="9135220303720555525">"Stäng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Helskärm"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Flytta BIB"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
index 615209f..0ee2841 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programu isiyo na jina)"</string>
<string name="pip_close" msgid="9135220303720555525">"Funga PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Skrini nzima"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Kuhamisha PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
index 71c242c..8bcc43b 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(தலைப்பு இல்லை)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPஐ மூடு"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"முழுத்திரை"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIPபை நகர்த்து"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings_tv.xml b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
index f2dfb39..6e80bd7 100644
--- a/libs/WindowManager/Shell/res/values-te/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings_tv.xml
@@ -20,7 +20,6 @@
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"పిక్చర్-ఇన్-పిక్చర్"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(శీర్షిక లేని ప్రోగ్రామ్)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIPని మూసివేయి"</string>
- <string name="pip_fullscreen" msgid="7278047353591302554">"పూర్తి స్క్రీన్"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_fullscreen" msgid="7278047353591302554">"ఫుల్-స్క్రీన్"</string>
+ <string name="pip_move" msgid="1544227837964635439">"PIPను తరలించండి"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings_tv.xml b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
index e810c88..b6f6369 100644
--- a/libs/WindowManager/Shell/res/values-th/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ไม่มีชื่อรายการ)"</string>
<string name="pip_close" msgid="9135220303720555525">"ปิด PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"เต็มหน้าจอ"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"ย้าย PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
index 11d2953..71ca230 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Walang pamagat na programa)"</string>
<string name="pip_close" msgid="9135220303720555525">"Isara ang PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Ilipat ang PIP"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
index 6ed6e9f..e6ae7f1 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Başlıksız program)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP\'yi kapat"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Tam ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP\'yi taşı"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
index 482f59a..97e1f09 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Програма без назви)"</string>
<string name="pip_close" msgid="9135220303720555525">"Закрити PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"На весь екран"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Перемістити картинку в картинці"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
index c1954c7..1418570 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(بلا عنوان پروگرام)"</string>
<string name="pip_close" msgid="9135220303720555525">"PIP بند کریں"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"فُل اسکرین"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIP کو منتقل کریں"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
index 5140552..31c762e 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Nomsiz)"</string>
<string name="pip_close" msgid="9135220303720555525">"Kadr ichida kadr – chiqish"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Butun ekran"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"PIPni siljitish"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
index e54d866..b46cd49 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Không có chương trình tiêu đề)"</string>
<string name="pip_close" msgid="9135220303720555525">"Đóng PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Toàn màn hình"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Di chuyển PIP (Ảnh trong ảnh)"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
index 9ce1e6c..b6fec63 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(节目没有标题)"</string>
<string name="pip_close" msgid="9135220303720555525">"关闭画中画"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全屏"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"移动画中画窗口"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
index 9846772..b5d54cb 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(沒有標題的節目)"</string>
<string name="pip_close" msgid="9135220303720555525">"關閉 PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"移動畫中畫"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
index 7314d48..57db7a8 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(無標題的節目)"</string>
<string name="pip_close" msgid="9135220303720555525">"關閉子母畫面"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"全螢幕"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"移動子母畫面"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
index 63d9dd5..646a488 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings_tv.xml
@@ -21,6 +21,5 @@
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Alukho uhlelo lwesihloko)"</string>
<string name="pip_close" msgid="9135220303720555525">"Vala i-PIP"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Iskrini esigcwele"</string>
- <!-- no translation found for pip_move (1544227837964635439) -->
- <skip />
+ <string name="pip_move" msgid="1544227837964635439">"Hambisa i-PIP"</string>
</resources>
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 2b685bf..4cce87a 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -32,8 +32,6 @@
using namespace android::uirenderer::renderthread;
-static constexpr bool sEnableExtraCropInset = true;
-
namespace android {
namespace uirenderer {
@@ -66,20 +64,6 @@
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
return CopyResult::SourceEmpty;
}
-
- if (sEnableExtraCropInset &&
- (cropRect.right - cropRect.left != bitmap->width() ||
- cropRect.bottom - cropRect.top != bitmap->height())) {
- /*
- * When we need use filtering, we should also make border shrink here like gui.
- * But we could not check format for YUV or RGB here... Just use 1 pix.
- */
- cropRect.left += 0.5f;
- cropRect.top += 0.5f;
- cropRect.right -= 0.5f;
- cropRect.bottom -= 0.5f;
- }
-
UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
AHardwareBuffer_Desc description;
AHardwareBuffer_describe(sourceBuffer.get(), &description);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c4cef4c..a2704f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7242,6 +7242,24 @@
/**
* @hide
+ * Indicates whether a platform supports the Ultrasound feature which covers the playback
+ * and recording of 20kHz~ sounds. If platform supports Ultrasound, then the
+ * usage will be
+ * To start the Ultrasound playback:
+ * - Create an AudioTrack with {@link AudioAttributes.CONTENT_TYPE_ULTRASOUND}.
+ * To start the Ultrasound capture:
+ * - Create an AudioRecord with {@link MediaRecorder.AudioSource.ULTRASOUND}.
+ *
+ * @return whether the ultrasound feature is supported, true when platform supports both
+ * Ultrasound playback and capture, false otherwise.
+ */
+ @SystemApi
+ public static boolean isUltrasoundSupported() {
+ return AudioSystem.isUltrasoundSupported();
+ }
+
+ /**
+ * @hide
* Introspection API to retrieve audio product strategies.
* When implementing {Car|Oem}AudioManager, use this method to retrieve the collection of
* audio product strategies, which is indexed by a weakly typed index in order to be extended
@@ -7675,6 +7693,33 @@
}
/**
+ * Returns a list of direct {@link AudioProfile} that are supported for the specified
+ * {@link AudioAttributes}. This can be empty in case of an error or if no direct playback
+ * is possible.
+ *
+ * <p>Direct playback means that the audio stream is not resampled or downmixed
+ * by the framework. Checking for direct support can help the app select the representation
+ * of audio content that most closely matches the capabilities of the device and peripherals
+ * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded
+ * or mixed with other streams, if needed.
+ * <p>When using this information to inform your application which audio format to play,
+ * query again whenever audio output devices change (see {@link AudioDeviceCallback}).
+ * @param attributes a non-null {@link AudioAttributes} instance.
+ * @return a list of {@link AudioProfile}
+ */
+ @NonNull
+ public List<AudioProfile> getDirectProfilesForAttributes(@NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ ArrayList<AudioProfile> audioProfilesList = new ArrayList<>();
+ int status = AudioSystem.getDirectProfilesForAttributes(attributes, audioProfilesList);
+ if (status != SUCCESS) {
+ Log.w(TAG, "getDirectProfilesForAttributes failed.");
+ return new ArrayList<>();
+ }
+ return audioProfilesList;
+ }
+
+ /**
* @hide
* Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type provided.
* The type must be a valid output type defined in <code>AudioDeviceInfo</code> class,
@@ -8352,4 +8397,4 @@
return mHandler;
}
}
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 306479a..1b46a50 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1871,6 +1871,12 @@
/**
* @hide
+ * @see AudioManager#isUltrasoundSupported()
+ */
+ public static native boolean isUltrasoundSupported();
+
+ /**
+ * @hide
* Send audio HAL server process pids to native audioserver process for use
* when generating audio HAL servers tombstones
*/
@@ -2115,6 +2121,15 @@
AudioFormat format,
AudioDeviceAttributes[] devices);
+ /**
+ * @hide
+ * @param attributes audio attributes describing the playback use case
+ * @param audioProfilesList the list of AudioProfiles that can be played as direct output
+ * @return {@link #SUCCESS} if the list of AudioProfiles was successfully created (can be empty)
+ */
+ public static native int getDirectProfilesForAttributes(@NonNull AudioAttributes attributes,
+ @NonNull ArrayList<AudioProfile> audioProfilesList);
+
// Items shared with audio service
/**
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index b4fdcb9..3dbb8e09 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -19,6 +19,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
@@ -526,9 +529,17 @@
}
/**
- * Returns all encoder profiles of a camcorder profile for the given camera at
- * the given quality level.
- *
+ * This change id controls the kind of video profiles returned by {@link #getAll}.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long RETURN_ADVANCED_VIDEO_PROFILES = 206033068L; // buganizer id
+
+ /**
+ * Returns all basic encoder profiles of a camcorder profile for
+ * the given camera at the given quality level.
+ * <p>
* Quality levels QUALITY_LOW, QUALITY_HIGH are guaranteed to be supported, while
* other levels may or may not be supported. The supported levels can be checked using
* {@link #hasProfile(int, int)}.
@@ -537,19 +548,26 @@
* QUALITY_LOW/QUALITY_HIGH have to match one of qcif, cif, 480p, 720p, 1080p or 2160p.
* E.g. if the device supports 480p, 720p, 1080p and 2160p, then low is 480p and high is
* 2160p.
- *
+ * <p>
* The same is true for time lapse quality levels, i.e. QUALITY_TIME_LAPSE_LOW,
* QUALITY_TIME_LAPSE_HIGH are guaranteed to be supported and have to match one of
* qcif, cif, 480p, 720p, 1080p, or 2160p.
- *
+ * <p>
* For high speed quality levels, they may or may not be supported. If a subset of the levels
* are supported, QUALITY_HIGH_SPEED_LOW and QUALITY_HIGH_SPEED_HIGH are guaranteed to be
* supported and have to match one of 480p, 720p, or 1080p.
- *
+ * <p>
* A camcorder recording session with higher quality level usually has higher output
* bit rate, better video and/or audio recording quality, larger video frame
* resolution and higher audio sampling rate, etc, than those with lower quality
* level.
+ * <p>
+ * <b>Note:</b> as of {@link android.os.Build.VERSION_CODES#TIRAMISU Android TIRAMISU},
+ * this method can return advanced encoder profiles.
+ * <p>Apps targeting {@link Build.VERSION_CODES#S_V2} or before will only receive basic
+ * video encoder profiles that use output YUV 4:2:0 8-bit content.
+ * Apps targeting {@link Build.VERSION_CODES#TIRAMISU} or above will also receive advanced
+ * video encoder profiles that may output 10-bit, YUV 4:2:2/4:4:4 or HDR content.
*
* @param cameraId the id for the camera. Numeric camera ids from the list received by invoking
* {@link CameraManager#getCameraIdList} can be used as long as they are
@@ -602,7 +620,9 @@
} catch (NumberFormatException e) {
return null;
}
- return native_get_camcorder_profiles(id, quality);
+ return native_get_camcorder_profiles(
+ id, quality,
+ CompatChanges.isChangeEnabled(RETURN_ADVANCED_VIDEO_PROFILES));
}
/**
@@ -712,7 +732,7 @@
private static native final CamcorderProfile native_get_camcorder_profile(
int cameraId, int quality);
private static native final EncoderProfiles native_get_camcorder_profiles(
- int cameraId, int quality);
+ int cameraId, int quality, boolean advanced);
private static native final boolean native_has_camcorder_profile(
int cameraId, int quality);
}
diff --git a/media/java/android/media/EncoderProfiles.java b/media/java/android/media/EncoderProfiles.java
index ac8c65e..3e26fc6f 100644
--- a/media/java/android/media/EncoderProfiles.java
+++ b/media/java/android/media/EncoderProfiles.java
@@ -16,8 +16,11 @@
package android.media;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -97,6 +100,12 @@
return MediaFormat.MIMETYPE_VIDEO_VP8;
} else if (codec == MediaRecorder.VideoEncoder.HEVC) {
return MediaFormat.MIMETYPE_VIDEO_HEVC;
+ } else if (codec == MediaRecorder.VideoEncoder.VP9) {
+ return MediaFormat.MIMETYPE_VIDEO_VP9;
+ } else if (codec == MediaRecorder.VideoEncoder.DOLBY_VISION) {
+ return MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION;
+ } else if (codec == MediaRecorder.VideoEncoder.AV1) {
+ return MediaFormat.MIMETYPE_VIDEO_AV1;
}
// we should never be here
throw new RuntimeException("Unknown codec");
@@ -190,19 +199,73 @@
return profile;
}
+ /**
+ * The bit depth of the encoded video.
+ * <p>
+ * This value is effectively 8 or 10, but some devices may
+ * support additional values.
+ */
+ public int getBitDepth() {
+ return bitDepth;
+ }
+
+ /**
+ * The chroma subsampling of the encoded video.
+ * <p>
+ * For most devices this is always YUV_420 but some devices may
+ * support additional values.
+ *
+ * @see #YUV_420
+ * @see #YUV_422
+ * @see #YUV_444
+ */
+ public @ChromaSubsampling int getChromaSubsampling() {
+ return chromaSubsampling;
+ }
+
+ /**
+ * The HDR format of the encoded video.
+ * <p>
+ * This is one of the HDR_ values.
+ * @see #HDR_NONE
+ * @see #HDR_HLG
+ * @see #HDR_HDR10
+ * @see #HDR_HDR10PLUS
+ * @see #HDR_DOLBY_VISION
+ */
+ public @HdrFormat int getHdrFormat() {
+ return hdrFormat;
+ }
+
// Constructor called by JNI and CamcorderProfile
/* package private */ VideoProfile(int codec,
int width,
int height,
int frameRate,
int bitrate,
- int profile) {
+ int profile,
+ int chromaSubsampling,
+ int bitDepth,
+ int hdrFormat) {
this.codec = codec;
this.width = width;
this.height = height;
this.frameRate = frameRate;
this.bitrate = bitrate;
this.profile = profile;
+ this.chromaSubsampling = chromaSubsampling;
+ this.bitDepth = bitDepth;
+ this.hdrFormat = hdrFormat;
+ }
+
+ /* package private */ VideoProfile(int codec,
+ int width,
+ int height,
+ int frameRate,
+ int bitrate,
+ int profile) {
+ this(codec, width, height, frameRate, bitrate, profile,
+ YUV_420, 8 /* bitDepth */, HDR_NONE);
}
private int codec;
@@ -211,6 +274,81 @@
private int frameRate;
private int bitrate;
private int profile;
+ private int chromaSubsampling;
+ private int bitDepth;
+ private int hdrFormat;
+
+ /** @hide */
+ @IntDef({
+ HDR_NONE,
+ HDR_HLG,
+ HDR_HDR10,
+ HDR_HDR10PLUS,
+ HDR_DOLBY_VISION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HdrFormat {}
+
+ /** Not HDR (SDR).
+ * <p>
+ * An HDR format specifying SDR (Standard Dynamic
+ * Range) recording. */
+ public static final int HDR_NONE = 0;
+
+ /** HLG (Hybrid-Log Gamma).
+ * <p>
+ * An HDR format specifying HLG. */
+ public static final int HDR_HLG = 1;
+
+ /** HDR10.
+ * <p>
+ * An HDR format specifying HDR10. */
+ public static final int HDR_HDR10 = 2;
+
+ /** HDR10+.
+ * <p>
+ * An HDR format specifying HDR10+. */
+ public static final int HDR_HDR10PLUS = 3;
+
+ /**
+ * Dolby Vision
+ * <p>
+ * An HDR format specifying Dolby Vision. For this format
+ * the codec is always a Dolby Vision encoder. The encoder
+ * profile specifies which Dolby Vision version is being
+ * used.
+ *
+ * @see #getProfile
+ */
+ public static final int HDR_DOLBY_VISION = 4;
+
+ /** @hide */
+ @IntDef({
+ YUV_420,
+ YUV_422,
+ YUV_444,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChromaSubsampling {}
+
+
+ /** YUV 4:2:0.
+ * <p>
+ * A chroma subsampling where the U and V planes are subsampled
+ * by 2 both horizontally and vertically. */
+ public static final int YUV_420 = 0;
+
+ /** YUV 4:2:2.
+ * <p>
+ * A chroma subsampling where the U and V planes are subsampled
+ * by 2 horizontally alone. */
+ public static final int YUV_422 = 1;
+
+ /** YUV 4:4:4.
+ * <p>
+ * A chroma subsampling where the U and V planes are not
+ * subsampled. */
+ public static final int YUV_444 = 2;
}
/**
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5891a18..e18642c 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2526,7 +2526,7 @@
}
if (datetime == null) return -1;
return datetime.getTime();
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
return -1;
}
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 3fd27d5..5e300c8 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -459,6 +459,25 @@
public static final int COLOR_FormatSurface = 0x7F000789;
/**
+ * 64 bits per pixel RGBA color format, with 16-bit signed
+ * floating point red, green, blue, and alpha components.
+ * <p>
+ *
+ * <pre>
+ * byte byte byte byte
+ * <-- i -->|<- i+1 ->|<- i+2 ->|<- i+3 ->|<- i+4 ->|<- i+5 ->|<- i+6 ->|<- i+7 ->
+ * +---------+---------+-------------------+---------+---------+---------+---------+
+ * | RED | GREEN | BLUE | ALPHA |
+ * +---------+---------+-------------------+---------+---------+---------+---------+
+ * 0 7 0 7 0 7 0 7 0 7 0 7 0 7 0 7
+ * </pre>
+ *
+ * This corresponds to {@link android.graphics.PixelFormat#RGBA_F16}.
+ */
+ @SuppressLint("AllUpper")
+ public static final int COLOR_Format64bitABGRFloat = 0x7F000F16;
+
+ /**
* 32 bits per pixel RGBA color format, with 8-bit red, green, blue, and alpha components.
* <p>
* Using 32-bit little-endian representation, colors stored as Red 7:0, Green 15:8,
@@ -476,6 +495,26 @@
public static final int COLOR_Format32bitABGR8888 = 0x7F00A000;
/**
+ * 32 bits per pixel RGBA color format, with 10-bit red, green,
+ * blue, and 2-bit alpha components.
+ * <p>
+ * Using 32-bit little-endian representation, colors stored as
+ * Red 9:0, Green 19:10, Blue 29:20, and Alpha 31:30.
+ * <pre>
+ * byte byte byte byte
+ * <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 ----->
+ * +-----------------+---+-------------+-------+---------+-----------+-----+
+ * | RED | GREEN | BLUE |ALPHA|
+ * +-----------------+---+-------------+-------+---------+-----------+-----+
+ * 0 7 0 1 2 7 0 3 4 7 0 5 6 7
+ * </pre>
+ *
+ * This corresponds to {@link android.graphics.PixelFormat#RGBA_1010102}.
+ */
+ @SuppressLint("AllUpper")
+ public static final int COLOR_Format32bitABGR2101010 = 0x7F00AAA2;
+
+ /**
* Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
* components.
* <p>
@@ -4005,6 +4044,12 @@
public static final int DolbyVisionLevelUhd30 = 0x40;
public static final int DolbyVisionLevelUhd48 = 0x80;
public static final int DolbyVisionLevelUhd60 = 0x100;
+ @SuppressLint("AllUpper")
+ public static final int DolbyVisionLevelUhd120 = 0x200;
+ @SuppressLint("AllUpper")
+ public static final int DolbyVisionLevel8k30 = 0x400;
+ @SuppressLint("AllUpper")
+ public static final int DolbyVisionLevel8k60 = 0x800;
// Profiles and levels for AV1 Codec, corresponding to the definitions in
// "AV1 Bitstream & Decoding Process Specification", Annex A
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 3a19b13..2a04ebb 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -41,9 +41,9 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
-import java.time.Instant;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
@@ -1864,7 +1864,16 @@
* <p>
* Each secure stop has a unique ID that can be used to identify it during
* enumeration, access and removal.
+ *
* @return a list of all secure stops from secure persistent memory
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Secure stops are a way to enforce limits on the number of
+ * concurrent streams per subscriber across devices. They provide secure
+ * monitoring of the lifetime of content decryption keys in MediaDrm
+ * sessions. Limits on concurrent streams may also be enforced by
+ * periodically renewing licenses. This can be achieved by calling
+ * {@link #getKeyRequest} to initiate a renewal. MediaDrm users should
+ * transition away from secure stops to periodic renewals.
*/
@NonNull
public native List<byte[]> getSecureStops();
@@ -1875,6 +1884,10 @@
* secure stop.
*
* @return a list of secure stop IDs
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Use renewals by calling {@link #getKeyRequest} to track
+ * concurrent playback. See additional information in
+ * {@link #getSecureStops}
*/
@NonNull
public native List<byte[]> getSecureStopIds();
@@ -1885,6 +1898,10 @@
*
* @param ssid the ID of the secure stop to return
* @return the secure stop identified by ssid
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Use renewals by calling {@link #getKeyRequest} to track
+ * concurrent playback. See additional information in
+ * {@link #getSecureStops}
*/
@NonNull
public native byte[] getSecureStop(@NonNull byte[] ssid);
@@ -1895,6 +1912,10 @@
* response.
*
* @param ssRelease the server response indicating which secure stops to release
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Use renewals by calling {@link #getKeyRequest} to track
+ * concurrent playback. See additional information in
+ * {@link #getSecureStops}
*/
public native void releaseSecureStops(@NonNull byte[] ssRelease);
@@ -1902,6 +1923,10 @@
* Remove a specific secure stop without requiring a secure stop release message
* from the license server.
* @param ssid the ID of the secure stop to remove
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Use renewals by calling {@link #getKeyRequest} to track
+ * concurrent playback. See additional information in
+ * {@link #getSecureStops}
*/
public native void removeSecureStop(@NonNull byte[] ssid);
@@ -1912,6 +1937,10 @@
* This method was added in API 28. In API versions 18 through 27,
* {@link #releaseAllSecureStops} should be called instead. There is no need to
* do anything for API versions prior to 18.
+ * @deprecated This method is deprecated and may be removed in a future
+ * release. Use renewals by calling {@link #getKeyRequest} to track
+ * concurrent playback. See additional information in
+ * {@link #getSecureStops}
*/
public native void removeAllSecureStops();
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index d7857a0..f1e6038 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -705,6 +705,9 @@
public static final int MPEG_4_SP = 3;
public static final int VP8 = 4;
public static final int HEVC = 5;
+ public static final int VP9 = 6;
+ public static final int DOLBY_VISION = 7;
+ public static final int AV1 = 8;
}
/**
@@ -717,6 +720,9 @@
VideoEncoder.MPEG_4_SP,
VideoEncoder.VP8,
VideoEncoder.HEVC,
+ VideoEncoder.VP9,
+ VideoEncoder.DOLBY_VISION,
+ VideoEncoder.AV1,
})
@Retention(RetentionPolicy.SOURCE)
public @interface VideoEncoderValues {}
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 6ba9133..f7a52f2 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -18,19 +18,34 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @hide */
+/**
+ * A request for the information retrieved from broadcast signal.
+ */
+@SuppressLint("ParcelNotFinal")
public abstract class BroadcastInfoRequest implements Parcelable {
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({REQUEST_OPTION_REPEAT, REQUEST_OPTION_AUTO_UPDATE})
public @interface RequestOption {}
+ /**
+ * Request option: repeat.
+ * <p>With this option, a response is sent when related broadcast information is detected,
+ * even if the same information has been sent previously.
+ */
public static final int REQUEST_OPTION_REPEAT = 0;
+ /**
+ * Request option: auto update.
+ * <p>With this option, a response is sent only when broadcast information is detected for the
+ * first time, new values are detected.
+ */
public static final int REQUEST_OPTION_AUTO_UPDATE = 1;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
@@ -66,32 +81,54 @@
}
};
- protected final @TvInputManager.BroadcastInfoType int mType;
- protected final int mRequestId;
- protected final @RequestOption int mOption;
+ private final @TvInputManager.BroadcastInfoType int mType;
+ private final int mRequestId;
+ private final @RequestOption int mOption;
- protected BroadcastInfoRequest(@TvInputManager.BroadcastInfoType int type,
+ BroadcastInfoRequest(@TvInputManager.BroadcastInfoType int type,
int requestId, @RequestOption int option) {
mType = type;
mRequestId = requestId;
mOption = option;
}
- protected BroadcastInfoRequest(@TvInputManager.BroadcastInfoType int type, Parcel source) {
+ BroadcastInfoRequest(@TvInputManager.BroadcastInfoType int type, Parcel source) {
mType = type;
mRequestId = source.readInt();
mOption = source.readInt();
}
- public @TvInputManager.BroadcastInfoType int getType() {
+ /**
+ * Gets the broadcast info type.
+ *
+ * <p>The type indicates what broadcast information is requested, such as broadcast table,
+ * PES (packetized Elementary Stream), TS (transport stream), etc. The type of the
+ * request and the related responses should be the same.
+ */
+ @TvInputManager.BroadcastInfoType
+ public int getType() {
return mType;
}
+ /**
+ * Gets the ID of the request.
+ *
+ * <p>The ID is used to associate the response with the request.
+ *
+ * @see android.media.tv.BroadcastInfoResponse#getRequestId()
+ */
public int getRequestId() {
return mRequestId;
}
- public @RequestOption int getOption() {
+ /**
+ * Gets the request option of the request.
+ *
+ * @see #REQUEST_OPTION_REPEAT
+ * @see #REQUEST_OPTION_AUTO_UPDATE
+ */
+ @RequestOption
+ public int getOption() {
return mOption;
}
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index 67bdedc..ff4ec15 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -18,20 +18,35 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @hide */
+/**
+ * A response of {@link BroadcastInfoRequest} for information retrieved from broadcast signal.
+ */
+@SuppressLint("ParcelNotFinal")
public abstract class BroadcastInfoResponse implements Parcelable {
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({RESPONSE_RESULT_ERROR, RESPONSE_RESULT_OK, RESPONSE_RESULT_CANCEL})
public @interface ResponseResult {}
+ /**
+ * Response result: error. This means the request can not be set up successfully.
+ */
public static final int RESPONSE_RESULT_ERROR = 1;
+ /**
+ * Response result: OK. This means the request is set up successfully and the related responses
+ * are normal responses.
+ */
public static final int RESPONSE_RESULT_OK = 2;
+ /**
+ * Response result: cancel. This means the request has been cancelled.
+ */
public static final int RESPONSE_RESULT_CANCEL = 3;
public static final @NonNull Parcelable.Creator<BroadcastInfoResponse> CREATOR =
@@ -67,12 +82,12 @@
}
};
- protected final @TvInputManager.BroadcastInfoType int mType;
- protected final int mRequestId;
- protected final int mSequence;
- protected final @ResponseResult int mResponseResult;
+ private final @TvInputManager.BroadcastInfoType int mType;
+ private final int mRequestId;
+ private final int mSequence;
+ private final @ResponseResult int mResponseResult;
- protected BroadcastInfoResponse(@TvInputManager.BroadcastInfoType int type, int requestId,
+ BroadcastInfoResponse(@TvInputManager.BroadcastInfoType int type, int requestId,
int sequence, @ResponseResult int responseResult) {
mType = type;
mRequestId = requestId;
@@ -80,26 +95,52 @@
mResponseResult = responseResult;
}
- protected BroadcastInfoResponse(@TvInputManager.BroadcastInfoType int type, Parcel source) {
+ BroadcastInfoResponse(@TvInputManager.BroadcastInfoType int type, Parcel source) {
mType = type;
mRequestId = source.readInt();
mSequence = source.readInt();
mResponseResult = source.readInt();
}
- public @TvInputManager.BroadcastInfoType int getType() {
+ /**
+ * Gets the broadcast info type.
+ *
+ * <p>The type indicates what broadcast information is requested, such as broadcast table,
+ * PES (packetized Elementary Stream), TS (transport stream), etc. The type of the
+ * request and the related responses should be the same.
+ */
+ @TvInputManager.BroadcastInfoType
+ public int getType() {
return mType;
}
+ /**
+ * Gets the ID of the request.
+ *
+ * <p>The ID is used to associate the response with the request.
+ *
+ * @see android.media.tv.BroadcastInfoRequest#getRequestId()
+ */
public int getRequestId() {
return mRequestId;
}
+ /**
+ * Gets the sequence number which indicates the order of related responses.
+ */
public int getSequence() {
return mSequence;
}
- public @ResponseResult int getResponseResult() {
+ /**
+ * Gets the result for the response.
+ *
+ * @see #RESPONSE_RESULT_OK
+ * @see #RESPONSE_RESULT_ERROR
+ * @see #RESPONSE_RESULT_CANCEL
+ */
+ @ResponseResult
+ public int getResponseResult() {
return mResponseResult;
}
diff --git a/media/java/android/media/tv/CommandRequest.java b/media/java/android/media/tv/CommandRequest.java
index d61c858..ffb6e07 100644
--- a/media/java/android/media/tv/CommandRequest.java
+++ b/media/java/android/media/tv/CommandRequest.java
@@ -20,9 +20,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for command from broadcast signal.
+ */
public final class CommandRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
public static final @NonNull Parcelable.Creator<CommandRequest> CREATOR =
@@ -43,38 +45,56 @@
private final String mName;
private final String mArguments;
- public static CommandRequest createFromParcelBody(Parcel in) {
+ static CommandRequest createFromParcelBody(Parcel in) {
return new CommandRequest(in);
}
- public CommandRequest(int requestId, @RequestOption int option, String nameSpace,
- String name, String arguments) {
- super(requestType, requestId, option);
+ public CommandRequest(int requestId, @RequestOption int option, @NonNull String nameSpace,
+ @NonNull String name, @NonNull String arguments) {
+ super(REQUEST_TYPE, requestId, option);
mNameSpace = nameSpace;
mName = name;
mArguments = arguments;
}
- protected CommandRequest(Parcel source) {
- super(requestType, source);
+ CommandRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
mNameSpace = source.readString();
mName = source.readString();
mArguments = source.readString();
}
+ /**
+ * Gets the namespace of the command.
+ */
+ @NonNull
public String getNameSpace() {
return mNameSpace;
}
+ /**
+ * Gets the name of the command.
+ */
+ @NonNull
public String getName() {
return mName;
}
+ /**
+ * Gets the arguments of the command.
+ * It could be serialized from some formats, such as JSON, XML, etc.
+ */
+ @NonNull
public String getArguments() {
return mArguments;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mNameSpace);
diff --git a/media/java/android/media/tv/CommandResponse.java b/media/java/android/media/tv/CommandResponse.java
index af3d00c..c8853f1 100644
--- a/media/java/android/media/tv/CommandResponse.java
+++ b/media/java/android/media/tv/CommandResponse.java
@@ -17,12 +17,15 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for command from broadcast signal.
+ */
public final class CommandResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_COMMAND;
public static final @NonNull Parcelable.Creator<CommandResponse> CREATOR =
@@ -41,26 +44,36 @@
private final String mResponse;
- public static CommandResponse createFromParcelBody(Parcel in) {
+ static CommandResponse createFromParcelBody(Parcel in) {
return new CommandResponse(in);
}
public CommandResponse(int requestId, int sequence,
- @ResponseResult int responseResult, String response) {
- super(responseType, requestId, sequence, responseResult);
+ @ResponseResult int responseResult, @Nullable String response) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mResponse = response;
}
- protected CommandResponse(Parcel source) {
- super(responseType, source);
+ CommandResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
mResponse = source.readString();
}
+ /**
+ * Gets the response of the command.
+ * It could be serialized from some formats, such as JSON, XML, etc.
+ */
+ @Nullable
public String getResponse() {
return mResponse;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mResponse);
diff --git a/media/java/android/media/tv/DsmccRequest.java b/media/java/android/media/tv/DsmccRequest.java
index 6bb1472..0425939 100644
--- a/media/java/android/media/tv/DsmccRequest.java
+++ b/media/java/android/media/tv/DsmccRequest.java
@@ -21,9 +21,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for DSM-CC from broadcast signal.
+ */
public final class DsmccRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_DSMCC;
public static final @NonNull Parcelable.Creator<DsmccRequest> CREATOR =
@@ -42,26 +44,35 @@
private final Uri mUri;
- public static DsmccRequest createFromParcelBody(Parcel in) {
+ static DsmccRequest createFromParcelBody(Parcel in) {
return new DsmccRequest(in);
}
- public DsmccRequest(int requestId, @RequestOption int option, Uri uri) {
- super(requestType, requestId, option);
+ public DsmccRequest(int requestId, @RequestOption int option, @NonNull Uri uri) {
+ super(REQUEST_TYPE, requestId, option);
mUri = uri;
}
- protected DsmccRequest(Parcel source) {
- super(requestType, source);
+ DsmccRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
String uriString = source.readString();
mUri = uriString == null ? null : Uri.parse(uriString);
}
+ /**
+ * Gets the URI for DSM-CC object.
+ */
+ @NonNull
public Uri getUri() {
return mUri;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
String uriString = mUri == null ? null : mUri.toString();
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
index 3ca63e3..e14e8791 100644
--- a/media/java/android/media/tv/DsmccResponse.java
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -17,6 +17,7 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringDef;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
@@ -27,9 +28,11 @@
import java.util.ArrayList;
import java.util.List;
-/** @hide */
+/**
+ * A response for DSM-CC from broadcast signal.
+ */
public final class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_DSMCC;
/** @hide */
@@ -73,7 +76,7 @@
private final int[] mEventIds;
private final String[] mEventNames;
- public static DsmccResponse createFromParcelBody(Parcel in) {
+ static DsmccResponse createFromParcelBody(Parcel in) {
return new DsmccResponse(in);
}
@@ -81,8 +84,8 @@
* Constructs a BIOP file message response.
*/
public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
- @NonNull ParcelFileDescriptor file) {
- super(responseType, requestId, sequence, responseResult);
+ @Nullable ParcelFileDescriptor file) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mBiopMessageType = BIOP_MESSAGE_TYPE_FILE;
mFileDescriptor = file;
mChildList = null;
@@ -94,8 +97,8 @@
* Constructs a BIOP service gateway or directory message response.
*/
public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
- boolean isServiceGateway, @NonNull List<String> childList) {
- super(responseType, requestId, sequence, responseResult);
+ boolean isServiceGateway, @Nullable List<String> childList) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
if (isServiceGateway) {
mBiopMessageType = BIOP_MESSAGE_TYPE_SERVICE_GATEWAY;
} else {
@@ -114,8 +117,8 @@
* stream event message type.
*/
public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
- @NonNull int[] eventIds, @NonNull String[] eventNames) {
- super(responseType, requestId, sequence, responseResult);
+ @Nullable int[] eventIds, @Nullable String[] eventNames) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM;
mFileDescriptor = null;
mChildList = null;
@@ -127,7 +130,7 @@
}
private DsmccResponse(@NonNull Parcel source) {
- super(responseType, source);
+ super(RESPONSE_TYPE, source);
mBiopMessageType = source.readString();
switch (mBiopMessageType) {
@@ -164,13 +167,17 @@
}
}
- /** Returns the BIOP message type */
+ /**
+ * Returns the BIOP message type.
+ */
@NonNull
public @BiopMessageType String getBiopMessageType() {
return mBiopMessageType;
}
- /** Returns the file descriptor for a given file message response */
+ /**
+ * Returns the file descriptor for a given file message response.
+ */
@NonNull
public ParcelFileDescriptor getFile() {
if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_FILE)) {
@@ -192,7 +199,9 @@
return new ArrayList<String>(mChildList);
}
- /** Returns all event IDs carried in a given stream message response. */
+ /**
+ * Returns all event IDs carried in a given stream message response.
+ */
@NonNull
public int[] getStreamEventIds() {
if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
@@ -201,7 +210,9 @@
return mEventIds;
}
- /** Returns all event names carried in a given stream message response */
+ /**
+ * Returns all event names carried in a given stream message response.
+ */
@NonNull
public String[] getStreamEventNames() {
if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
@@ -211,6 +222,11 @@
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mBiopMessageType);
diff --git a/media/java/android/media/tv/PesRequest.java b/media/java/android/media/tv/PesRequest.java
index 7dedb65..bacfaa3 100644
--- a/media/java/android/media/tv/PesRequest.java
+++ b/media/java/android/media/tv/PesRequest.java
@@ -20,9 +20,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for PES from broadcast signal.
+ */
public final class PesRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_PES;
public static final @NonNull Parcelable.Creator<PesRequest> CREATOR =
@@ -42,31 +44,42 @@
private final int mTsPid;
private final int mStreamId;
- public static PesRequest createFromParcelBody(Parcel in) {
+ static PesRequest createFromParcelBody(Parcel in) {
return new PesRequest(in);
}
public PesRequest(int requestId, @RequestOption int option, int tsPid, int streamId) {
- super(requestType, requestId, option);
+ super(REQUEST_TYPE, requestId, option);
mTsPid = tsPid;
mStreamId = streamId;
}
- protected PesRequest(Parcel source) {
- super(requestType, source);
+ PesRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
mTsPid = source.readInt();
mStreamId = source.readInt();
}
+ /**
+ * Gets the packet identifier (PID) of the TS (transport stream).
+ */
public int getTsPid() {
return mTsPid;
}
+ /**
+ * Gets the StreamID of requested PES.
+ */
public int getStreamId() {
return mStreamId;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mTsPid);
diff --git a/media/java/android/media/tv/PesResponse.java b/media/java/android/media/tv/PesResponse.java
index a657f91..9f420b1 100644
--- a/media/java/android/media/tv/PesResponse.java
+++ b/media/java/android/media/tv/PesResponse.java
@@ -17,12 +17,15 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for PES from broadcast signal.
+ */
public final class PesResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_PES;
public static final @NonNull Parcelable.Creator<PesResponse> CREATOR =
@@ -41,26 +44,35 @@
private final String mSharedFilterToken;
- public static PesResponse createFromParcelBody(Parcel in) {
+ static PesResponse createFromParcelBody(Parcel in) {
return new PesResponse(in);
}
public PesResponse(int requestId, int sequence, @ResponseResult int responseResult,
- String sharedFilterToken) {
- super(responseType, requestId, sequence, responseResult);
+ @Nullable String sharedFilterToken) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mSharedFilterToken = sharedFilterToken;
}
- protected PesResponse(Parcel source) {
- super(responseType, source);
+ PesResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
mSharedFilterToken = source.readString();
}
+ /**
+ * Gets the token for a shared filter from Tv Input Service.
+ */
+ @Nullable
public String getSharedFilterToken() {
return mSharedFilterToken;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mSharedFilterToken);
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
index 533c509..5957528 100644
--- a/media/java/android/media/tv/SectionRequest.java
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -20,9 +20,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for Section from broadcast signal.
+ */
public final class SectionRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_SECTION;
public static final @NonNull Parcelable.Creator<SectionRequest> CREATOR =
@@ -41,44 +43,58 @@
private final int mTsPid;
private final int mTableId;
- private final Integer mVersion;
+ private final int mVersion;
- public static SectionRequest createFromParcelBody(Parcel in) {
+ static SectionRequest createFromParcelBody(Parcel in) {
return new SectionRequest(in);
}
public SectionRequest(int requestId, @RequestOption int option, int tsPid, int tableId,
- Integer version) {
- super(requestType, requestId, option);
+ int version) {
+ super(REQUEST_TYPE, requestId, option);
mTsPid = tsPid;
mTableId = tableId;
mVersion = version;
}
- protected SectionRequest(Parcel source) {
- super(requestType, source);
+ SectionRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
mTsPid = source.readInt();
mTableId = source.readInt();
- mVersion = (Integer) source.readValue(Integer.class.getClassLoader());
+ mVersion = source.readInt();
}
+ /**
+ * Gets the packet identifier (PID) of the TS (transport stream).
+ */
public int getTsPid() {
return mTsPid;
}
+ /**
+ * Gets the ID of the requested table.
+ */
public int getTableId() {
return mTableId;
}
- public Integer getVersion() {
+ /**
+ * Gets the version number of requested session. If it is null, value will be -1.
+ */
+ public int getVersion() {
return mVersion;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mTsPid);
dest.writeInt(mTableId);
- dest.writeValue(mVersion);
+ dest.writeInt(mVersion);
}
}
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
index d3fa3c0..35836be 100644
--- a/media/java/android/media/tv/SectionResponse.java
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -17,13 +17,16 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for Section from broadcast signal.
+ */
public final class SectionResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_SECTION;
public static final @NonNull Parcelable.Creator<SectionResponse> CREATOR =
@@ -44,38 +47,53 @@
private final int mVersion;
private final Bundle mSessionData;
- public static SectionResponse createFromParcelBody(Parcel in) {
+ static SectionResponse createFromParcelBody(Parcel in) {
return new SectionResponse(in);
}
public SectionResponse(int requestId, int sequence, @ResponseResult int responseResult,
- int sessionId, int version, Bundle sessionData) {
- super(responseType, requestId, sequence, responseResult);
+ int sessionId, int version, @Nullable Bundle sessionData) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mSessionId = sessionId;
mVersion = version;
mSessionData = sessionData;
}
- protected SectionResponse(Parcel source) {
- super(responseType, source);
+ SectionResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
mSessionId = source.readInt();
mVersion = source.readInt();
mSessionData = source.readBundle();
}
+ /**
+ * Gets the Session Id of requested session.
+ */
public int getSessionId() {
return mSessionId;
}
+ /**
+ * Gets the Version number of requested session.
+ */
public int getVersion() {
return mVersion;
}
+ /**
+ * Gets the raw data of session.
+ */
+ @NonNull
public Bundle getSessionData() {
return mSessionData;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mSessionId);
diff --git a/media/java/android/media/tv/StreamEventRequest.java b/media/java/android/media/tv/StreamEventRequest.java
index 84a5bee..8a46128 100644
--- a/media/java/android/media/tv/StreamEventRequest.java
+++ b/media/java/android/media/tv/StreamEventRequest.java
@@ -21,9 +21,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for Stream Event from broadcast signal.
+ */
public final class StreamEventRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_STREAM_EVENT;
public static final @NonNull Parcelable.Creator<StreamEventRequest> CREATOR =
@@ -43,33 +45,46 @@
private final Uri mTargetUri;
private final String mEventName;
- public static StreamEventRequest createFromParcelBody(Parcel in) {
+ static StreamEventRequest createFromParcelBody(Parcel in) {
return new StreamEventRequest(in);
}
- public StreamEventRequest(int requestId, @RequestOption int option, Uri targetUri,
- String eventName) {
- super(requestType, requestId, option);
+ public StreamEventRequest(int requestId, @RequestOption int option, @NonNull Uri targetUri,
+ @NonNull String eventName) {
+ super(REQUEST_TYPE, requestId, option);
this.mTargetUri = targetUri;
this.mEventName = eventName;
}
- protected StreamEventRequest(Parcel source) {
- super(requestType, source);
+ StreamEventRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
String uriString = source.readString();
mTargetUri = uriString == null ? null : Uri.parse(uriString);
mEventName = source.readString();
}
+ /**
+ * Gets the URI for the DSM-CC Object or the event description file describing the event.
+ */
+ @NonNull
public Uri getTargetUri() {
return mTargetUri;
}
+ /**
+ * Gets the name of the event.
+ */
+ @NonNull
public String getEventName() {
return mEventName;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
String uriString = mTargetUri == null ? null : mTargetUri.toString();
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
index 903fab5c2..f952ce9 100644
--- a/media/java/android/media/tv/StreamEventResponse.java
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -17,12 +17,15 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for Stream Event from broadcast signal.
+ */
public final class StreamEventResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_STREAM_EVENT;
public static final @NonNull Parcelable.Creator<StreamEventResponse> CREATOR =
@@ -43,20 +46,20 @@
private final long mNpt;
private final byte[] mData;
- public static StreamEventResponse createFromParcelBody(Parcel in) {
+ static StreamEventResponse createFromParcelBody(Parcel in) {
return new StreamEventResponse(in);
}
public StreamEventResponse(int requestId, int sequence, @ResponseResult int responseResult,
- int eventId, long npt, @NonNull byte[] data) {
- super(responseType, requestId, sequence, responseResult);
+ int eventId, long npt, @Nullable byte[] data) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mEventId = eventId;
mNpt = npt;
mData = data;
}
private StreamEventResponse(@NonNull Parcel source) {
- super(responseType, source);
+ super(RESPONSE_TYPE, source);
mEventId = source.readInt();
mNpt = source.readLong();
int dataLength = source.readInt();
@@ -64,23 +67,34 @@
source.readByteArray(mData);
}
- /** Returns the event ID */
+ /**
+ * Returns the event ID.
+ */
public int getEventId() {
return mEventId;
}
- /** Returns the NPT(Normal Play Time) value when the event occurred or will occur */
+ /**
+ * Returns the NPT(Normal Play Time) value when the event occurred or will occur.
+ */
public long getNpt() {
return mNpt;
}
- /** Returns the application specific data */
- @NonNull
+ /**
+ * Returns the application specific data.
+ */
+ @Nullable
public byte[] getData() {
return mData;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mEventId);
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index 389536d..37df4ea 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -24,11 +24,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @hide */
+/**
+ * A request for Table from broadcast signal.
+ */
public final class TableRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TABLE;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT})
public @interface TableName {}
@@ -54,38 +57,52 @@
private final @TableName int mTableName;
private final int mVersion;
- public static TableRequest createFromParcelBody(Parcel in) {
+ static TableRequest createFromParcelBody(Parcel in) {
return new TableRequest(in);
}
public TableRequest(int requestId, @RequestOption int option, int tableId,
@TableName int tableName, int version) {
- super(requestType, requestId, option);
+ super(REQUEST_TYPE, requestId, option);
mTableId = tableId;
mTableName = tableName;
mVersion = version;
}
- protected TableRequest(Parcel source) {
- super(requestType, source);
+ TableRequest(Parcel source) {
+ super(REQUEST_TYPE, source);
mTableId = source.readInt();
mTableName = source.readInt();
mVersion = source.readInt();
}
+ /**
+ * Gets the ID of requested table.
+ */
public int getTableId() {
return mTableId;
}
+ /**
+ * Gets the name of requested table.
+ */
public @TableName int getTableName() {
return mTableName;
}
+ /**
+ * Gets the version number of requested table.
+ */
public int getVersion() {
return mVersion;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mTableId);
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index 68d5f8a..e9f1136 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -17,13 +17,16 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for Table from broadcast signal.
+ */
public final class TableResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TABLE;
public static final @NonNull Parcelable.Creator<TableResponse> CREATOR =
@@ -44,39 +47,54 @@
private final int mVersion;
private final int mSize;
- public static TableResponse createFromParcelBody(Parcel in) {
+ static TableResponse createFromParcelBody(Parcel in) {
return new TableResponse(in);
}
public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
- Uri tableUri, int version, int size) {
- super(responseType, requestId, sequence, responseResult);
+ @Nullable Uri tableUri, int version, int size) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
mTableUri = tableUri;
mVersion = version;
mSize = size;
}
- protected TableResponse(Parcel source) {
- super(responseType, source);
+ TableResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
String uriString = source.readString();
mTableUri = uriString == null ? null : Uri.parse(uriString);
mVersion = source.readInt();
mSize = source.readInt();
}
+ /**
+ * Gets the URI in TvProvider database.
+ */
+ @Nullable
public Uri getTableUri() {
return mTableUri;
}
+ /**
+ * Gets the Version number of table.
+ */
public int getVersion() {
return mVersion;
}
+ /**
+ * Gets the Size number of table.
+ */
public int getSize() {
return mSize;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
String uriString = mTableUri == null ? null : mTableUri.toString();
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
index 0714972..03c62f0 100644
--- a/media/java/android/media/tv/TimelineRequest.java
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -20,7 +20,9 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for Timeline from broadcast signal.
+ */
public final class TimelineRequest extends BroadcastInfoRequest implements Parcelable {
private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
@@ -39,24 +41,27 @@
}
};
- private final int mIntervalMs;
+ private final int mIntervalMillis;
static TimelineRequest createFromParcelBody(Parcel in) {
return new TimelineRequest(in);
}
- public TimelineRequest(int requestId, @RequestOption int option, int intervalMs) {
+ public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis) {
super(REQUEST_TYPE, requestId, option);
- mIntervalMs = intervalMs;
+ mIntervalMillis = intervalMillis;
}
- protected TimelineRequest(Parcel source) {
+ TimelineRequest(Parcel source) {
super(REQUEST_TYPE, source);
- mIntervalMs = source.readInt();
+ mIntervalMillis = source.readInt();
}
- public int getIntervalMs() {
- return mIntervalMs;
+ /**
+ * Gets the interval of TIS sending response to TIAS in millisecond.
+ */
+ public int getIntervalMillis() {
+ return mIntervalMillis;
}
@Override
@@ -67,6 +72,6 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeInt(mIntervalMs);
+ dest.writeInt(mIntervalMillis);
}
}
diff --git a/media/java/android/media/tv/TimelineResponse.java b/media/java/android/media/tv/TimelineResponse.java
index fee10b4..fbeb0c4 100644
--- a/media/java/android/media/tv/TimelineResponse.java
+++ b/media/java/android/media/tv/TimelineResponse.java
@@ -17,10 +17,13 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for Timeline from broadcast signal.
+ */
public final class TimelineResponse extends BroadcastInfoResponse implements Parcelable {
private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TIMELINE;
@@ -50,7 +53,7 @@
}
public TimelineResponse(int requestId, int sequence,
- @ResponseResult int responseResult, String selector, int unitsPerTick,
+ @ResponseResult int responseResult, @Nullable String selector, int unitsPerTick,
int unitsPerSecond, long wallClock, long ticks) {
super(RESPONSE_TYPE, requestId, sequence, responseResult);
mSelector = selector;
@@ -60,7 +63,7 @@
mTicks = ticks;
}
- protected TimelineResponse(Parcel source) {
+ TimelineResponse(Parcel source) {
super(RESPONSE_TYPE, source);
mSelector = source.readString();
mUnitsPerTick = source.readInt();
@@ -69,22 +72,43 @@
mTicks = source.readLong();
}
+ /**
+ * Gets the Timeline Selector of the response.
+ * The Timeline Selector is a URI that specifies the source of a Timeline
+ * by indicating its type and information needed to locate the signalling
+ * that conveys Time Values on it.
+ */
+ @Nullable
public String getSelector() {
return mSelector;
}
+ /**
+ * Gets the UnitsPerTick of the response.
+ */
public int getUnitsPerTick() {
return mUnitsPerTick;
}
+ /**
+ * Gets the UnitsPerSecond of the response.
+ */
public int getUnitsPerSecond() {
return mUnitsPerSecond;
}
+ /**
+ * Gets the System time (UTC) of the response.
+ */
public long getWallClock() {
return mWallClock;
}
+ /**
+ * Gets the Ticks of the response.
+ * A Time Value is a measure of a moment in time for a particular Timeline.
+ * Time Values are represented by an integer number of ticks (positive or negative).
+ */
public long getTicks() {
return mTicks;
}
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
index 99350c9..4a1dccb 100644
--- a/media/java/android/media/tv/TsRequest.java
+++ b/media/java/android/media/tv/TsRequest.java
@@ -20,9 +20,11 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A request for TS (transport stream) from broadcast signal.
+ */
public final class TsRequest extends BroadcastInfoRequest implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int requestType =
+ private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TS;
public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
@@ -41,25 +43,33 @@
private final int mTsPid;
- public static TsRequest createFromParcelBody(Parcel in) {
+ static @NonNull TsRequest createFromParcelBody(@NonNull Parcel in) {
return new TsRequest(in);
}
public TsRequest(int requestId, @RequestOption int option, int tsPid) {
- super(requestType, requestId, option);
+ super(REQUEST_TYPE, requestId, option);
mTsPid = tsPid;
}
- protected TsRequest(Parcel source) {
- super(requestType, source);
+ TsRequest(@NonNull Parcel source) {
+ super(REQUEST_TYPE, source);
mTsPid = source.readInt();
}
+ /**
+ * Gets the packet identifier (PID) of the TS (transport stream).
+ */
public int getTsPid() {
return mTsPid;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mTsPid);
diff --git a/media/java/android/media/tv/TsResponse.java b/media/java/android/media/tv/TsResponse.java
index c5ec53a..abcd736 100644
--- a/media/java/android/media/tv/TsResponse.java
+++ b/media/java/android/media/tv/TsResponse.java
@@ -17,12 +17,15 @@
package android.media.tv;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * A response for TS (transport stream) from broadcast signal.
+ */
public final class TsResponse extends BroadcastInfoResponse implements Parcelable {
- public static final @TvInputManager.BroadcastInfoType int responseType =
+ private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE =
TvInputManager.BROADCAST_INFO_TYPE_TS;
public static final @NonNull Parcelable.Creator<TsResponse> CREATOR =
@@ -41,26 +44,37 @@
private final String mSharedFilterToken;
- public static TsResponse createFromParcelBody(Parcel in) {
+ static TsResponse createFromParcelBody(@NonNull Parcel in) {
return new TsResponse(in);
}
public TsResponse(int requestId, int sequence, @ResponseResult int responseResult,
- String sharedFilterToken) {
- super(responseType, requestId, sequence, responseResult);
+ @Nullable String sharedFilterToken) {
+ super(RESPONSE_TYPE, requestId, sequence, responseResult);
this.mSharedFilterToken = sharedFilterToken;
}
- protected TsResponse(Parcel source) {
- super(responseType, source);
+ TsResponse(Parcel source) {
+ super(RESPONSE_TYPE, source);
mSharedFilterToken = source.readString();
}
+ /**
+ * Gets a token of SharedFilter.
+ *
+ * <p>The token can be used to retrieve the transport stream from the filter.
+ */
+ @Nullable
public String getSharedFilterToken() {
return mSharedFilterToken;
}
@Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(mSharedFilterToken);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index cc33a1e..41666c7 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -369,21 +369,13 @@
BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE})
public @interface BroadcastInfoType {}
- /** @hide */
public static final int BROADCAST_INFO_TYPE_TS = 1;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_TABLE = 2;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_SECTION = 3;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_PES = 4;
- /** @hide */
public static final int BROADCAST_INFO_STREAM_EVENT = 5;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_DSMCC = 6;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_COMMAND = 7;
- /** @hide */
public static final int BROADCAST_INFO_TYPE_TIMELINE = 8;
/** @hide */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index f63f444..70acf25 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -106,9 +106,9 @@
* @hide
*/
@IntDef(prefix = "PRIORITY_HINT_USE_CASE_TYPE_",
- value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, PRIORITY_HINT_USE_CASE_TYPE_SCAN,
- PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, PRIORITY_HINT_USE_CASE_TYPE_LIVE,
- PRIORITY_HINT_USE_CASE_TYPE_RECORD})
+ value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, PRIORITY_HINT_USE_CASE_TYPE_SCAN,
+ PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+ PRIORITY_HINT_USE_CASE_TYPE_RECORD})
@Retention(RetentionPolicy.SOURCE)
public @interface PriorityHintUseCaseType {}
@@ -872,7 +872,6 @@
* Notifies response for broadcast info.
*
* @param response broadcast info response.
- * @hide
*/
public void notifyBroadcastInfoResponse(@NonNull final BroadcastInfoResponse response) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1111,13 +1110,12 @@
* called when broadcast info is requested.
*
* @param request broadcast info request
- * @hide
*/
public void onRequestBroadcastInfo(@NonNull BroadcastInfoRequest request) {
}
/**
- * @hide
+ * called when broadcast info is removed.
*/
public void onRemoveBroadcastInfo(int requestId) {
}
@@ -2291,43 +2289,43 @@
private final TvInputManager.SessionCallback mHardwareSessionCallback =
new TvInputManager.SessionCallback() {
- @Override
- public void onSessionCreated(TvInputManager.Session session) {
- mHardwareSession = session;
- SomeArgs args = SomeArgs.obtain();
- if (session != null) {
- args.arg1 = HardwareSession.this;
- args.arg2 = mProxySession;
- args.arg3 = mProxySessionCallback;
- args.arg4 = session.getToken();
- session.tune(TvContract.buildChannelUriForPassthroughInput(
- getHardwareInputId()));
- } else {
- args.arg1 = null;
- args.arg2 = null;
- args.arg3 = mProxySessionCallback;
- args.arg4 = null;
- onRelease();
- }
- mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
- .sendToTarget();
- }
+ @Override
+ public void onSessionCreated(TvInputManager.Session session) {
+ mHardwareSession = session;
+ SomeArgs args = SomeArgs.obtain();
+ if (session != null) {
+ args.arg1 = HardwareSession.this;
+ args.arg2 = mProxySession;
+ args.arg3 = mProxySessionCallback;
+ args.arg4 = session.getToken();
+ session.tune(TvContract.buildChannelUriForPassthroughInput(
+ getHardwareInputId()));
+ } else {
+ args.arg1 = null;
+ args.arg2 = null;
+ args.arg3 = mProxySessionCallback;
+ args.arg4 = null;
+ onRelease();
+ }
+ mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
+ args).sendToTarget();
+ }
- @Override
- public void onVideoAvailable(final TvInputManager.Session session) {
- if (mHardwareSession == session) {
- onHardwareVideoAvailable();
- }
- }
+ @Override
+ public void onVideoAvailable(final TvInputManager.Session session) {
+ if (mHardwareSession == session) {
+ onHardwareVideoAvailable();
+ }
+ }
- @Override
- public void onVideoUnavailable(final TvInputManager.Session session,
- final int reason) {
- if (mHardwareSession == session) {
- onHardwareVideoUnavailable(reason);
- }
- }
- };
+ @Override
+ public void onVideoUnavailable(final TvInputManager.Session session,
+ final int reason) {
+ if (mHardwareSession == session) {
+ onHardwareVideoUnavailable(reason);
+ }
+ }
+ };
/**
* This method will not be called in {@link HardwareSession}. Framework will
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 819707a..57730ac 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -556,7 +556,6 @@
/**
* Called when a broadcast info response is received.
- * @hide
*/
public void onBroadcastInfoResponse(@NonNull BroadcastInfoResponse response) {
}
@@ -659,7 +658,6 @@
/**
* Requests broadcast related information from the related TV input.
* @param request the request for broadcast info
- * @hide
*/
public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
executeOrPostRunnableOnMainThread(new Runnable() {
@@ -684,7 +682,6 @@
/**
* Remove broadcast information request from the related TV input.
* @param requestId the ID of the request
- * @hide
*/
public void removeBroadcastInfo(final int requestId) {
executeOrPostRunnableOnMainThread(new Runnable() {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index be114d4..ffc4433 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -1565,9 +1565,9 @@
}
/**
- * Filter out unnecessary PID (packet identifier) from frontend output.
+ * Remove PID (packet identifier) from frontend output.
*
- * <p>It is used by the client to remove some video or audio PIDs of other program to reduce the
+ * <p>It is used by the client to remove a video or audio PID of other program to reduce the
* total amount of recorded TS.
*
* <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index 90325e7..f2c48a0 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -225,7 +225,7 @@
static jobject
android_media_MediaProfiles_native_get_camcorder_profiles(JNIEnv *env, jobject /* thiz */, jint id,
- jint quality)
+ jint quality, jboolean advanced)
{
ALOGV("native_get_camcorder_profiles: %d %d", id, quality);
if (!isCamcorderQualityKnown(quality)) {
@@ -251,7 +251,7 @@
jclass videoProfileClazz = env->FindClass("android/media/EncoderProfiles$VideoProfile");
jmethodID videoProfileConstructorMethodID =
- env->GetMethodID(videoProfileClazz, "<init>", "(IIIIII)V");
+ env->GetMethodID(videoProfileClazz, "<init>", "(IIIIIIIII)V");
jclass audioProfileClazz = env->FindClass("android/media/EncoderProfiles$AudioProfile");
jmethodID audioProfileConstructorMethodID =
@@ -262,6 +262,16 @@
{
int i = 0;
for (const MediaProfiles::VideoCodec *vc : cp->getVideoCodecs()) {
+ chroma_subsampling cs = vc->getChromaSubsampling();
+ int bitDepth = vc->getBitDepth();
+ hdr_format hdr = vc->getHdrFormat();
+
+ bool isAdvanced =
+ (bitDepth != 8 || cs != CHROMA_SUBSAMPLING_YUV_420 || hdr != HDR_FORMAT_NONE);
+ if (static_cast<bool>(advanced) && !isAdvanced) {
+ continue;
+ }
+
jobject videoCodec = env->NewObject(videoProfileClazz,
videoProfileConstructorMethodID,
vc->getCodec(),
@@ -269,7 +279,10 @@
vc->getFrameHeight(),
vc->getFrameRate(),
vc->getBitrate(),
- vc->getProfile());
+ vc->getProfile(),
+ static_cast<int>(cs),
+ bitDepth,
+ static_cast<int>(hdr));
env->SetObjectArrayElement(videoCodecs, i++, videoCodec);
}
}
@@ -400,7 +413,7 @@
{"native_init", "()V", (void *)android_media_MediaProfiles_native_init},
{"native_get_camcorder_profile", "(II)Landroid/media/CamcorderProfile;",
(void *)android_media_MediaProfiles_native_get_camcorder_profile},
- {"native_get_camcorder_profiles", "(II)Landroid/media/EncoderProfiles;",
+ {"native_get_camcorder_profiles", "(IIZ)Landroid/media/EncoderProfiles;",
(void *)android_media_MediaProfiles_native_get_camcorder_profiles},
{"native_has_camcorder_profile", "(II)Z",
(void *)android_media_MediaProfiles_native_has_camcorder_profile},
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index 00a5210..34b573d 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
<string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 2254e1f..d79b653 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
<string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 5944dba3..7988111 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
<string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index e58aed7..17e2cb1 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিয়ক"</string>
<string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 7577776..9d504f1 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
<string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index 8a63b11..63b5094 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
<string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index bf4fe3e..bd6ead2 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
<string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index cc67b13..5f5320e 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
<string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 08ffab0..8bc47eb 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
<string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 8b0daaa..8644645 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
<string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index c98feb3..9a5d4b8 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
<string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index c758b6e..0210500 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
<string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index b026bb1..7e89735 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
<string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 345b971..97f017e 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Zulassen"</string>
<string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 64d500e..926f7151 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
<string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 1756d22..e9452fd 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index efda04e..2ed5310 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -32,4 +32,6 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
<string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
+ <string name="permission_sync_confirmation_title" msgid="667074294393493186">"Transfer app permissions to your watch"</string>
+ <string name="permission_sync_summary" msgid="8873391306499120778">"To make it easier to set up your watch, apps installed on your watch during setup will use the same permissions as your phone.\n\n These permissions may include access to your watch’s microphone and location."</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 90e33a5..705615d 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 78ac63f..b682490 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 165dc97..34c0fb2 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
<string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index d424359..808baf4 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
<string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index d9053fd..6dea7ef 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"مجاز بودن"</string>
<string name="consent_no" msgid="2640796915611404382">"مجاز نبودن"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index e76f89d..5772ebf 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
<string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index f6a4855..c09f1d6 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
<string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index a214b89..63dd6a3 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
<string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index c179378..8b31f97 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index ff9a89e..077ff27 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
<string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 557e1f8..57f18cd 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
<string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index 453a4dd..a8bc9e6 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
<string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index dacc4e4..a862475 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
<string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index 9b79f4b..4eefc0b 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
<string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 684167e..533e81d 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
<string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index cdfc47a..25438ce 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
<string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index fc7100a..8f23b6a 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
<string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 295df783..ec21a10 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
<string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index a9438be..f6ef81a 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"許可"</string>
<string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 8354f4a..9440227 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
<string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 722b570..e99a61c 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
<string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index d47d6c4..0f8820e 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
<string name="consent_no" msgid="2640796915611404382">"កុំអនុញ្ញាត"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index ba9f8ff..81e956d 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
<string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 8faab71..b2e5062 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"허용"</string>
<string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index eec1775..6f05848 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Уруксат берүү"</string>
<string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index ed24422..314329f 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
<string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 8472d79..b3c789c 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
<string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 8b27a08..be7a95e 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
<string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index e6131e6..29d9660 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
<string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index e35a733..ec09d65 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
<string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 1ea1c9b..f27698c 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
<string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 1936ede..685250d 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
<string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index fb69cb1..e594d61 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
<string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 31596a4..7d3ef90 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
<string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 52afcf0..23c7fbf 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
<string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index 9b42c1e..4615733 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"अनुमति दिनुहोस्"</string>
<string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 354cb93..83acc79 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
<string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index b58ebd34..8d3bb65 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index f2a5c29..692d67f 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ਇਜਾਜ਼ਤ ਦਿਓ"</string>
<string name="consent_no" msgid="2640796915611404382">"ਇਜਾਜ਼ਤ ਨਾ ਦਿਓ"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 9356792..3de6c5b 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
<string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index 7d79608..b440215 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index bc30ed8..73982a6 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index 7d79608..b440215 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index dd38f1f..d3e725f 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Permiteți"</string>
<string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index 8e2b4d8..5983a59 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
<string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index 489ecf9..83a5156 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
<string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index cbee372..3fe111c 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
<string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 53eb85d..a3c9a07 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
<string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 0704b9b..bb9ae13 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
<string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index eb768a2..6da288c 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
<string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index 24db58d..5c821f2 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
<string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index d06f1c6..588addc 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
<string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index d58d2ae..9bbc9f5 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
<string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 9e9fec5..759eded 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"అనుమతించు"</string>
<string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 9d9c91d..233e242 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
<string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 436097c..d5ee345 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
<string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index 3a256a7..6129ea9 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
<string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 9f40a0c..82aa0d7 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
<string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 3c1fe5d..db8b472 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"اجازت دیں"</string>
<string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index ff5e4b9..e937f87 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
<string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index f52dde1..b17f61a 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
<string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index f1facc1..61ffa09 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"允许"</string>
<string name="consent_no" msgid="2640796915611404382">"不允许"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index aed008f..6842261 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"允許"</string>
<string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 22a9d9c..c9449e6 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"允許"</string>
<string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index 5c5756b..e8ac64b 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -32,4 +32,8 @@
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
<string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
+ <!-- no translation found for permission_sync_confirmation_title (667074294393493186) -->
+ <skip />
+ <!-- no translation found for permission_sync_summary (8873391306499120778) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 25ec9606..f32f2cd 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -58,6 +58,14 @@
<!-- Description of the privileges the application will get if associated with the companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_automotive_projection"></string>
+ <!-- ================= DEVICE_PROFILE_COMPUTER ================= -->
+
+ <!-- Confirmation for associating an application with a companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
+ <string name="title_computer">Allow <strong><xliff:g id="app_name" example="GMS">%1$s</xliff:g></strong> to access this information from your phone</string>
+
+ <!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
+ <string name="summary_computer"></string>
+
<!-- ================= null profile ================= -->
<!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 8d14172..27c14af 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -18,6 +18,7 @@
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -317,6 +318,12 @@
this, R.string.summary_automotive_projection, appLabel, deviceName);
break;
+ case DEVICE_PROFILE_COMPUTER:
+ title = getHtmlFromResources(this, R.string.title_computer, appLabel);
+ summary = getHtmlFromResources(
+ this, R.string.summary_computer, appLabel, deviceName);
+ break;
+
default:
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 28f930f..84adef5 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -145,6 +145,18 @@
/** @hide */
public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 2;
+ /**
+ * Virtual RAT type to represent 5G NSA (Non Stand Alone) mode, where the primary cell is
+ * still LTE and network allocates a secondary 5G cell so telephony reports RAT = LTE along
+ * with NR state as connected. This is a concept added by NetworkStats on top of the telephony
+ * constants for backward compatibility of metrics so this should not be overlapped with any of
+ * the {@code TelephonyManager.NETWORK_TYPE_*} constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int NETWORK_TYPE_5G_NSA = -2;
+
private int mFlags;
/** @hide */
@@ -1111,4 +1123,52 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Get a RAT type representative of a group of RAT types for network statistics.
+ *
+ * Collapse the given Radio Access Technology (RAT) type into a bucket that
+ * is representative of the original RAT type for network statistics. The
+ * mapping mostly corresponds to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}
+ * but with adaptations specific to the virtual types introduced by
+ * networks stats.
+ *
+ * @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static int getCollapsedRatType(int ratType) {
+ switch (ratType) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_GSM:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return TelephonyManager.NETWORK_TYPE_GSM;
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+ return TelephonyManager.NETWORK_TYPE_UMTS;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
+ return TelephonyManager.NETWORK_TYPE_LTE;
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return TelephonyManager.NETWORK_TYPE_NR;
+ // Virtual RAT type for 5G NSA mode, see
+ // {@link NetworkStatsManager#NETWORK_TYPE_5G_NSA}.
+ case NetworkStatsManager.NETWORK_TYPE_5G_NSA:
+ return NetworkStatsManager.NETWORK_TYPE_5G_NSA;
+ default:
+ return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ }
+ }
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 77fc171..73b9c72 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.service.NetworkIdentityProto;
@@ -435,7 +436,7 @@
public Builder setRatType(@Annotation.NetworkType int ratType) {
if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
&& ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN
- && ratType != NetworkTemplate.NETWORK_TYPE_5G_NSA) {
+ && ratType != NetworkStatsManager.NETWORK_TYPE_5G_NSA) {
throw new IllegalArgumentException("Invalid ratType " + ratType);
}
mRatType = ratType;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index dba3991..27e717f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -41,13 +41,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.usage.NetworkStatsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -58,9 +58,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Comparator;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -136,15 +134,6 @@
* {@code TelephonyManager.NETWORK_TYPE_*} constants, and thus needs to stay in sync.
*/
public static final int NETWORK_TYPE_ALL = -1;
- /**
- * Virtual RAT type to represent 5G NSA (Non Stand Alone) mode, where the primary cell is
- * still LTE and network allocates a secondary 5G cell so telephony reports RAT = LTE along
- * with NR state as connected. This should not be overlapped with any of the
- * {@code TelephonyManager.NETWORK_TYPE_*} constants.
- *
- * @hide
- */
- public static final int NETWORK_TYPE_5G_NSA = -2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -711,7 +700,8 @@
private boolean matchesCollapsedRatType(NetworkIdentity ident) {
return mRatType == NETWORK_TYPE_ALL
- || getCollapsedRatType(mRatType) == getCollapsedRatType(ident.mRatType);
+ || NetworkStatsManager.getCollapsedRatType(mRatType)
+ == NetworkStatsManager.getCollapsedRatType(ident.mRatType);
}
/**
@@ -755,84 +745,6 @@
}
/**
- * Get a Radio Access Technology(RAT) type that is representative of a group of RAT types.
- * The mapping is corresponding to {@code TelephonyManager#NETWORK_CLASS_BIT_MASK_*}.
- *
- * @param ratType An integer defined in {@code TelephonyManager#NETWORK_TYPE_*}.
- *
- * @hide
- */
- // TODO: 1. Consider move this to TelephonyManager if used by other modules.
- // 2. Consider make this configurable.
- // 3. Use TelephonyManager APIs when available.
- // TODO: @SystemApi when ready.
- public static int getCollapsedRatType(int ratType) {
- switch (ratType) {
- case TelephonyManager.NETWORK_TYPE_GPRS:
- case TelephonyManager.NETWORK_TYPE_GSM:
- case TelephonyManager.NETWORK_TYPE_EDGE:
- case TelephonyManager.NETWORK_TYPE_IDEN:
- case TelephonyManager.NETWORK_TYPE_CDMA:
- case TelephonyManager.NETWORK_TYPE_1xRTT:
- return TelephonyManager.NETWORK_TYPE_GSM;
- case TelephonyManager.NETWORK_TYPE_EVDO_0:
- case TelephonyManager.NETWORK_TYPE_EVDO_A:
- case TelephonyManager.NETWORK_TYPE_EVDO_B:
- case TelephonyManager.NETWORK_TYPE_EHRPD:
- case TelephonyManager.NETWORK_TYPE_UMTS:
- case TelephonyManager.NETWORK_TYPE_HSDPA:
- case TelephonyManager.NETWORK_TYPE_HSUPA:
- case TelephonyManager.NETWORK_TYPE_HSPA:
- case TelephonyManager.NETWORK_TYPE_HSPAP:
- case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
- return TelephonyManager.NETWORK_TYPE_UMTS;
- case TelephonyManager.NETWORK_TYPE_LTE:
- case TelephonyManager.NETWORK_TYPE_IWLAN:
- return TelephonyManager.NETWORK_TYPE_LTE;
- case TelephonyManager.NETWORK_TYPE_NR:
- return TelephonyManager.NETWORK_TYPE_NR;
- // Virtual RAT type for 5G NSA mode, see {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}.
- case NetworkTemplate.NETWORK_TYPE_5G_NSA:
- return NetworkTemplate.NETWORK_TYPE_5G_NSA;
- default:
- return TelephonyManager.NETWORK_TYPE_UNKNOWN;
- }
- }
-
- /**
- * Return all supported collapsed RAT types that could be returned by
- * {@link #getCollapsedRatType(int)}.
- *
- * @hide
- */
- // TODO: @SystemApi when ready.
- @NonNull
- public static final int[] getAllCollapsedRatTypes() {
- final int[] ratTypes = TelephonyManager.getAllNetworkTypes();
- final HashSet<Integer> collapsedRatTypes = new HashSet<>();
- for (final int ratType : ratTypes) {
- collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(ratType));
- }
- // Add NETWORK_TYPE_5G_NSA to the returned list since 5G NSA is a virtual RAT type and
- // it is not in TelephonyManager#NETWORK_TYPE_* constants.
- // See {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}.
- collapsedRatTypes.add(NetworkTemplate.getCollapsedRatType(NETWORK_TYPE_5G_NSA));
- // Ensure that unknown type is returned.
- collapsedRatTypes.add(TelephonyManager.NETWORK_TYPE_UNKNOWN);
- return toIntArray(collapsedRatTypes);
- }
-
- @NonNull
- private static int[] toIntArray(@NonNull Collection<Integer> list) {
- final int[] array = new int[list.size()];
- int i = 0;
- for (final Integer item : list) {
- array[i++] = item;
- }
- return array;
- }
-
- /**
* Check if matches Wi-Fi network template.
*/
private boolean matchesWifi(NetworkIdentity ident) {
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 243d621..8e584d0 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -60,7 +60,6 @@
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
-import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -121,6 +120,7 @@
import android.provider.Settings.Global;
import android.service.NetworkInterfaceProto;
import android.service.NetworkStatsServiceDumpProto;
+import android.system.ErrnoException;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionPlan;
import android.text.TextUtils;
@@ -139,10 +139,14 @@
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.BestClock;
import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
import java.io.File;
import java.io.FileDescriptor;
@@ -207,6 +211,10 @@
private static final String NETSTATS_COMBINE_SUBTYPE_ENABLED =
"netstats_combine_subtype_enabled";
+ // This is current path but may be changed soon.
+ private static final String UID_COUNTERSET_MAP_PATH =
+ "/sys/fs/bpf/map_netd_uid_counterset_map";
+
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
private final AlarmManager mAlarmManager;
@@ -244,7 +252,7 @@
/**
* When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}.
* When disabled, mobile data is broken down by a granular ratType representative of the
- * actual ratType. {@see NetworkTemplate#getCollapsedRatType}.
+ * actual ratType. {@see android.app.usage.NetworkStatsManager#getCollapsedRatType}.
* Enabling this decreases the level of detail but saves performance, disk space and
* amount of data logged.
*/
@@ -325,8 +333,14 @@
@GuardedBy("mStatsLock")
private NetworkStatsCollection mXtStatsCached;
- /** Current counter sets for each UID. */
+ /**
+ * Current counter sets for each UID.
+ * TODO: maybe remove mActiveUidCounterSet and read UidCouneterSet value from mUidCounterSetMap
+ * directly ? But if mActiveUidCounterSet would be accessed very frequently, maybe keep
+ * mActiveUidCounterSet to avoid accessing kernel too frequently.
+ */
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
+ private final IBpfMap<U32, U8> mUidCounterSetMap;
/** Data layer operation counters for splicing into other structures. */
private NetworkStats mUidOperations = new NetworkStats(0L, 10);
@@ -459,6 +473,7 @@
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
mInterfaceMapUpdater.start();
+ mUidCounterSetMap = mDeps.getUidCounterSetMap();
}
/**
@@ -520,6 +535,17 @@
@NonNull Context ctx, @NonNull Handler handler) {
return new BpfInterfaceMapUpdater(ctx, handler);
}
+
+ /** Get counter sets map for each UID. */
+ public IBpfMap<U32, U8> getUidCounterSetMap() {
+ try {
+ return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
+ U32.class, U8.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot create uid counter set map: " + e);
+ return null;
+ }
+ }
}
/**
@@ -1077,6 +1103,29 @@
}
}
+ private void setKernelCounterSet(int uid, int set) {
+ if (mUidCounterSetMap == null) {
+ Log.wtf(TAG, "Fail to set UidCounterSet: Null bpf map");
+ return;
+ }
+
+ if (set == SET_DEFAULT) {
+ try {
+ mUidCounterSetMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ Log.w(TAG, "UidCounterSetMap.deleteEntry(" + uid + ") failed with errno: " + e);
+ }
+ return;
+ }
+
+ try {
+ mUidCounterSetMap.updateEntry(new U32(uid), new U8((short) set));
+ } catch (ErrnoException e) {
+ Log.w(TAG, "UidCounterSetMap.updateEntry(" + uid + ", " + set
+ + ") failed with errno: " + e);
+ }
+ }
+
@VisibleForTesting
public void setUidForeground(int uid, boolean uidForeground) {
PermissionUtils.enforceNetworkStackPermission(mContext);
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 4875f1c..3e35e603 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -16,8 +16,8 @@
package com.android.server.net;
-import static android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA;
-import static android.net.NetworkTemplate.getCollapsedRatType;
+import static android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA;
+import static android.app.usage.NetworkStatsManager.getCollapsedRatType;
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED;
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
@@ -57,7 +57,7 @@
*
* @param subscriberId IMSI of the subscription.
* @param collapsedRatType collapsed RAT type.
- * @see android.net.NetworkTemplate#getCollapsedRatType(int).
+ * @see android.app.usage.NetworkStatsManager#getCollapsedRatType(int).
*/
void onCollapsedRatTypeChanged(@NonNull String subscriberId,
@Annotation.NetworkType int collapsedRatType);
diff --git a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
index 58a80a7..7ead44b 100644
--- a/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-gl/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Dynamic System Updates"</string>
+ <string name="keyguard_description" msgid="8582605799129954556">"Pon o teu contrasinal e vai a Actualizacións dinámicas do sistema"</string>
<string name="notification_install_completed" msgid="6252047868415172643">"O sistema dinámico está listo. Para utilizalo, reinicia o dispositivo."</string>
<string name="notification_install_inprogress" msgid="7383334330065065017">"Instalación en curso"</string>
<string name="notification_install_failed" msgid="4066039210317521404">"Produciuse un erro durante a instalación"</string>
diff --git a/packages/PrintSpooler/res/values-te/strings.xml b/packages/PrintSpooler/res/values-te/strings.xml
index a1ed2ca..038029d 100644
--- a/packages/PrintSpooler/res/values-te/strings.xml
+++ b/packages/PrintSpooler/res/values-te/strings.xml
@@ -88,7 +88,7 @@
<string name="no_connection_to_printer" msgid="2159246915977282728">"ప్రింటర్కు కనెక్షన్ లేదు"</string>
<string name="reason_unknown" msgid="5507940196503246139">"తెలియదు"</string>
<string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g>ని ఉపయోగించాలా?"</string>
- <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ పత్రం ప్రింటర్కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్ల గుండా వెళ్లవచ్చు."</string>
+ <string name="print_service_security_warning_summary" msgid="1427434625361692006">"మీ డాక్యుమెంట్ ప్రింటర్కు వెళ్లే మార్గంలో ఒకటి లేదా అంతకంటే ఎక్కువ సర్వర్ల గుండా వెళ్లవచ్చు."</string>
<string-array name="color_mode_labels">
<item msgid="7602948745415174937">"నలుపు & తెలుపు"</item>
<item msgid="2762241247228983754">"రంగు"</item>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 907863e..e3714db 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -1,71 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
+ 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.
+-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/app_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fitsSystemWindows="true"
- android:outlineAmbientShadowColor="@android:color/transparent"
- android:outlineSpotShadowColor="@android:color/transparent"
- android:background="?android:attr/colorPrimary"
- android:theme="@style/Theme.CollapsingToolbar.Settings">
-
- <com.google.android.material.appbar.CollapsingToolbarLayout
- android:id="@+id/collapsing_toolbar"
- android:layout_width="match_parent"
- android:layout_height="@dimen/settingslib_toolbar_layout_height"
- android:clipToPadding="false"
- app:forceApplySystemWindowInsetTop="true"
- app:extraMultilineHeightEnabled="true"
- app:contentScrim="@color/settingslib_colorSurfaceHeader"
- app:maxLines="3"
- app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
- app:scrimAnimationDuration="50"
- app:scrimVisibleHeightTrigger="@dimen/settingslib_scrim_visible_height_trigger"
- app:statusBarScrim="@null"
- app:titleCollapseMode="fade"
- app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
- app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle.Expanded"
- app:expandedTitleMarginStart="@dimen/expanded_title_margin_start"
- app:expandedTitleMarginEnd="@dimen/expanded_title_margin_end"
- app:toolbarId="@id/action_bar">
-
- <Toolbar
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:theme="?android:attr/actionBarTheme"
- android:transitionName="shared_element_view"
- app:layout_collapseMode="pin"/>
-
- </com.google.android.material.appbar.CollapsingToolbarLayout>
- </com.google.android.material.appbar.AppBarLayout>
-
- <FrameLayout
- android:id="@+id/content_frame"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+ <include layout="@layout/collapsing_toolbar_content_layout"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..25f0771
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ android:outlineAmbientShadowColor="@android:color/transparent"
+ android:outlineSpotShadowColor="@android:color/transparent"
+ android:background="?android:attr/colorPrimary"
+ android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+ <com.google.android.material.appbar.CollapsingToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/settingslib_toolbar_layout_height"
+ android:clipToPadding="false"
+ app:forceApplySystemWindowInsetTop="true"
+ app:extraMultilineHeightEnabled="true"
+ app:contentScrim="@color/settingslib_colorSurfaceHeader"
+ app:maxLines="3"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+ app:scrimAnimationDuration="50"
+ app:scrimVisibleHeightTrigger="@dimen/settingslib_scrim_visible_height_trigger"
+ app:statusBarScrim="@null"
+ app:titleCollapseMode="fade"
+ app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
+ app:expandedTitleTextAppearance="@style/CollapsingToolbarTitle.Expanded"
+ app:expandedTitleMarginStart="@dimen/expanded_title_margin_start"
+ app:expandedTitleMarginEnd="@dimen/expanded_title_margin_end"
+ app:toolbarId="@id/action_bar">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ android:transitionName="shared_element_view"
+ app:layout_collapseMode="pin"/>
+
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.xml
new file mode 100644
index 0000000..e57bff3
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/support_toolbar.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<androidx.appcompat.widget.Toolbar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/support_action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ android:transitionName="shared_element_view"
+ app:layout_collapseMode="pin" />
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml
new file mode 100644
index 0000000..6ddfb42
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/attrs.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <declare-styleable name="CollapsingCoordinatorLayout">
+ <!-- assign a title of collapsing toolbar title. -->
+ <attr name="collapsing_toolbar_title" format="string" />
+ <attr name="content_frame_height_match_parent" format="boolean" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
new file mode 100644
index 0000000..eec73ff
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.collapsingtoolbar.widget;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.coordinatorlayout.widget.CoordinatorLayout;
+
+import com.android.settingslib.collapsingtoolbar.R;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+/**
+ * This widget is wrapping the collapsing toolbar and can be directly used by the
+ * {@link AppCompatActivity}.
+ */
+public class CollapsingCoordinatorLayout extends CoordinatorLayout {
+ private static final String TAG = "CollapsingCoordinatorLayout";
+ private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
+
+ private CharSequence mToolbarTitle;
+ private boolean mIsMatchParentHeight;
+ private CollapsingToolbarLayout mCollapsingToolbarLayout;
+ private AppBarLayout mAppBarLayout;
+
+ public CollapsingCoordinatorLayout(@NonNull Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public CollapsingCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, /* defStyleAttr= */ 0);
+ }
+
+ public CollapsingCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mIsMatchParentHeight = false;
+ if (attrs != null) {
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.CollapsingCoordinatorLayout);
+ mToolbarTitle = a.getText(
+ R.styleable.CollapsingCoordinatorLayout_collapsing_toolbar_title);
+ mIsMatchParentHeight = a.getBoolean(
+ R.styleable.CollapsingCoordinatorLayout_content_frame_height_match_parent,
+ false);
+ a.recycle();
+ }
+ init();
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (child.getId() == R.id.content_frame && mIsMatchParentHeight) {
+ // User want to change the height of content_frame view as match_parent.
+ params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ final ViewGroup contentView = findViewById(R.id.content_frame);
+ if (contentView != null && isContentFrameChild(child.getId())) {
+ contentView.addView(child, index, params);
+ } else {
+ super.addView(child, index, params);
+ }
+ }
+
+ private boolean isContentFrameChild(int id) {
+ if (id == R.id.app_bar || id == R.id.content_frame) {
+ return false;
+ }
+ return true;
+ }
+
+ private void init() {
+ inflate(getContext(), R.layout.collapsing_toolbar_content_layout, this);
+ mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
+ mAppBarLayout = findViewById(R.id.app_bar);
+ if (mCollapsingToolbarLayout != null) {
+ mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
+ if (!TextUtils.isEmpty(mToolbarTitle)) {
+ mCollapsingToolbarLayout.setTitle(mToolbarTitle);
+ }
+ }
+ disableCollapsingToolbarLayoutScrollingBehavior();
+ }
+
+ /**
+ * Initialize some attributes of {@link ActionBar}.
+ *
+ * @param activity The host activity using the CollapsingCoordinatorLayout.
+ */
+ public void initSettingsStyleToolBar(Activity activity) {
+ if (activity == null) {
+ Log.w(TAG, "initSettingsStyleToolBar: activity is null");
+ return;
+ }
+
+ if (activity instanceof AppCompatActivity) {
+ initSupportToolbar((AppCompatActivity) activity);
+ return;
+ }
+
+ final Toolbar toolbar = findViewById(R.id.action_bar);
+ activity.setActionBar(toolbar);
+
+ // Enable title and home button by default
+ final ActionBar actionBar = activity.getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+ }
+
+ /**
+ * Initialize some attributes of {@link ActionBar} and assign the title of collapsing toolbar.
+ *
+ * @param activity The host activity using the CollapsingCoordinatorLayout.
+ * @param title The new title of collapsing toolbar.
+ */
+ public void initSettingsStyleToolBar(Activity activity, CharSequence title) {
+ if (activity == null) {
+ Log.w(TAG, "initSettingsStyleToolBar: activity is null");
+ return;
+ }
+ initSettingsStyleToolBar(activity);
+ if (!TextUtils.isEmpty(title) && mCollapsingToolbarLayout != null) {
+ mToolbarTitle = title;
+ mCollapsingToolbarLayout.setTitle(mToolbarTitle);
+ }
+ }
+
+ /**
+ * Returns an instance of collapsing toolbar.
+ */
+ public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+ return mCollapsingToolbarLayout;
+ }
+
+ /**
+ * Return an instance of app bar.
+ */
+ public AppBarLayout getAppBarLayout() {
+ return mAppBarLayout;
+ }
+
+ private void disableCollapsingToolbarLayoutScrollingBehavior() {
+ if (mAppBarLayout == null) {
+ return;
+ }
+ final CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
+ final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
+ behavior.setDragCallback(
+ new AppBarLayout.Behavior.DragCallback() {
+ @Override
+ public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
+ return false;
+ }
+ });
+ params.setBehavior(behavior);
+ }
+
+ // This API is for supportActionBar of {@link AppCompatActivity}
+ private void initSupportToolbar(AppCompatActivity appCompatActivity) {
+ if (mCollapsingToolbarLayout == null) {
+ return;
+ }
+
+ mCollapsingToolbarLayout.removeAllViews();
+ inflate(getContext(), R.layout.support_toolbar, mCollapsingToolbarLayout);
+ final androidx.appcompat.widget.Toolbar supportToolbar =
+ mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
+
+ appCompatActivity.setSupportActionBar(supportToolbar);
+
+ // Enable title and home button by default
+ final androidx.appcompat.app.ActionBar actionBar = appCompatActivity.getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+ }
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 13a5caf..7f5e8c4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nie geregistreer nie"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Onbeskikbaar"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC word ewekansig gemaak"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d toestelle is gekoppel</item>
- <item quantity="one">%1$d toestel is gekoppel</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 toestelle is gekoppel}=1{1 toestel is gekoppel}other{# toestelle is gekoppel}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meer tyd."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minder tyd."</string>
<string name="cancel" msgid="5665114069455378395">"Kanselleer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 7ad455a..0810032 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"አልተመዘገበም"</string>
<string name="status_unavailable" msgid="5279036186589861608">"አይገኝም"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"ማክ በዘፈቀደ ይሰራል"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d መሣሪያዎች ተገናኝተዋል</item>
- <item quantity="other">%1$d መሣሪያዎች ተገናኝተዋል</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ተጨማሪ ጊዜ።"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ያነሰ ጊዜ።"</string>
<string name="cancel" msgid="5665114069455378395">"ይቅር"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index a4646f1..6ebf521 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -526,14 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"غير مُسجَّل"</string>
<string name="status_unavailable" msgid="5279036186589861608">"غير متاح"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"يتم اختيار عنوان MAC بشكل انتقائي."</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="zero">عدد الأجهزة المتصلة %1$d</item>
- <item quantity="two">عدد الأجهزة المتصلة %1$d</item>
- <item quantity="few">عدد الأجهزة المتصلة %1$d</item>
- <item quantity="many">عدد الأجهزة المتصلة %1$d</item>
- <item quantity="other">عدد الأجهزة المتصلة %1$d</item>
- <item quantity="one">عدد الأجهزة المتصلة %1$d</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"وقت أكثر."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"وقت أقل."</string>
<string name="cancel" msgid="5665114069455378395">"إلغاء"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c304921..a5c9324 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"পঞ্জীকৃত নহয়"</string>
<string name="status_unavailable" msgid="5279036186589861608">"উপলব্ধ নহয়"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ক্ৰমানুসৰি ছেট কৰা হোৱা নাই"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$dটা ডিভাইচ সংযোগ হ’ল</item>
- <item quantity="other">%1$dটা ডিভাইচ সংযোগ হ’ল</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"অধিক সময়।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"কম সময়।"</string>
<string name="cancel" msgid="5665114069455378395">"বাতিল কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 6e3947e..eafb2cb 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Qeydiyyatsız"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Əlçatmazdır"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ixtiyari olaraq seçildi"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d cihaz qoşuludur</item>
- <item quantity="one">%1$d cihaz qoşuludur</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daha çox vaxt."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Daha az vaxt."</string>
<string name="cancel" msgid="5665114069455378395">"Ləğv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index a05dae9..21fcbdc 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -526,11 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrovan"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nedostupno"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa je nasumično izabrana"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Povezan je %1$d uređaj</item>
- <item quantity="few">Povezana su %1$d uređaja</item>
- <item quantity="other">Povezano je %1$d uređaja</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 uređaja je povezano}=1{1 uređaj je povezan}one{# uređaj je povezan}few{# uređaja su povezana}other{# uređaja je povezano}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
<string name="cancel" msgid="5665114069455378395">"Otkaži"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index e3b0567..8610554 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зарэгістраваны"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Адсутнічае"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Выпадковы MAC-адрас"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d прылада падключана</item>
- <item quantity="few">%1$d прылады падключаны</item>
- <item quantity="many">%1$d прылад падключана</item>
- <item quantity="other">%1$d прылады падключана</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Больш часу."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Менш часу."</string>
<string name="cancel" msgid="5665114069455378395">"Скасаваць"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index a8eaaf0..a2a5411 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не е регистрирано"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Няма данни"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC адресът е рандомизиран"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d устройства са свързани</item>
- <item quantity="one">%1$d устройство е свързано</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Повече време."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"По-малко време."</string>
<string name="cancel" msgid="5665114069455378395">"Отказ"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index c28e927..f7cf7e5 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"রেজিস্টার করা নয়"</string>
<string name="status_unavailable" msgid="5279036186589861608">"অনুপলভ্য"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC র্যান্ডমাইজ করা হয়েছে"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
- <item quantity="other">%1$dটি ডিভাইস কানেক্ট রয়েছে</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{০টি ডিভাইস কানেক্ট করা হয়েছে}=1{১টি ডিভাইস কানেক্ট করা হয়েছে}one{#টি ডিভাইস কানেক্ট করা হয়েছে}other{#টি ডিভাইস কানেক্ট করা হয়েছে}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"আরও বেশি।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"আরও কম।"</string>
<string name="cancel" msgid="5665114069455378395">"বাতিল"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 232c22f..3f70e82 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -526,11 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrirano"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nije dostupno"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa je nasumično odabrana"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Povezan je %1$d uređaj</item>
- <item quantity="few">Povezana su %1$duređaja</item>
- <item quantity="other">Povezano je %1$d uređaja</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Povezano je 0 uređaja}=1{Povezan je 1 uređaj}one{Povezan je # uređaj}few{Povezana su # uređaja}other{Povezano je # uređaja}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
<string name="cancel" msgid="5665114069455378395">"Otkaži"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index bc393c3..49de565 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Sense registrar"</string>
<string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"L\'adreça MAC és aleatòria"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d dispositius connectats</item>
- <item quantity="one">%1$d dispositiu connectat</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Més temps"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menys temps"</string>
<string name="cancel" msgid="5665114069455378395">"Cancel·la"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index f758365..bc755c5 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neregistrováno"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Není k dispozici"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC je vybrána náhodně"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="few">Připojena %1$d zařízení</item>
- <item quantity="many">Připojeno %1$d zařízení</item>
- <item quantity="other">Připojeno %1$d zařízení</item>
- <item quantity="one">Připojeno %1$d zařízení</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Delší doba"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kratší doba"</string>
<string name="cancel" msgid="5665114069455378395">"Zrušit"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 0fd2569..21dc551 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ikke registreret"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Utilgængelig"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adressen er tilfældig"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d enhed er tilsluttet</item>
- <item quantity="other">%1$d enheder er tilsluttet</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mere tid."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mindre tid."</string>
<string name="cancel" msgid="5665114069455378395">"Annuller"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index f75685b..bd6c74e 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nicht registriert"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nicht verfügbar"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-Adresse wird zufällig festgelegt"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d Geräte verbunden</item>
- <item quantity="one">%1$d Gerät verbunden</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 Geräte verbunden}=1{1 Gerät verbunden}other{# Geräte verbunden}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mehr Zeit."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Weniger Zeit."</string>
<string name="cancel" msgid="5665114069455378395">"Abbrechen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 5115728..2339f9b 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Μη εγγεγραμμένη"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Μη διαθέσιμο"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Η διεύθυνση MAC είναι τυχαία"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d συσκευές συνδέθηκαν</item>
- <item quantity="one">%1$d συσκευή συνδέθηκε</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 συνδεδεμένες συσκευές}=1{1 συνδεδεμένη συσκευή}other{# συνδεδεμένες συσκευές}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Περισσότερη ώρα."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Λιγότερη ώρα."</string>
<string name="cancel" msgid="5665114069455378395">"Ακύρωση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d devices connected</item>
- <item quantity="one">%1$d device connected</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
<string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b3dd58a..41bc981 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d devices connected</item>
- <item quantity="one">%1$d device connected</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
<string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d devices connected</item>
- <item quantity="one">%1$d device connected</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
<string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 1481cee..72100ee 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d devices connected</item>
- <item quantity="one">%1$d device connected</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
<string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 2a6c82c..e88b6b2 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d devices connected</item>
- <item quantity="one">%1$d device connected</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
<string name="cancel" msgid="5665114069455378395">"Cancel"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ee0e0bc..f3129c9 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Sin registrar"</string>
<string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"La dirección MAC es aleatoria"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d dispositivos conectados</item>
- <item quantity="one">%1$d dispositivo conectado</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Se conectaron 0 dispositivos}=1{Se conectó 1 dispositivo}other{Se conectaron # dispositivos}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Más tiempo"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tiempo"</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 1f64bad..26dd243 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"No registrado"</string>
<string name="status_unavailable" msgid="5279036186589861608">"No disponible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"La dirección MAC es aleatoria"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d dispositivos conectados</item>
- <item quantity="one">%1$d dispositivo conectado</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Más tiempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tiempo."</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index ed7b9dc..ccb2867 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ei ole registreeritud"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Pole saadaval"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-aadress on juhuslikuks muudetud"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d seadet on ühendatud</item>
- <item quantity="one">%1$d seade on ühendatud</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Pikem aeg."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Lühem aeg."</string>
<string name="cancel" msgid="5665114069455378395">"Tühista"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 5222d8a..e302bce 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Erregistratu gabe"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ez dago erabilgarri"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Ausaz aukeratutako MAC helbidea"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d gailu daude konektatuta</item>
- <item quantity="one">%1$d gailu dago konektatuta</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 gailu daude konektatuta}=1{1 gailu dago konektatuta}other{# gailu daude konektatuta}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Denbora gehiago."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Denbora gutxiago."</string>
<string name="cancel" msgid="5665114069455378395">"Utzi"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 1b5f979..bf6ca7d 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ثبت نشده است"</string>
<string name="status_unavailable" msgid="5279036186589861608">"در دسترس نیست"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"ویژگی MAC تصادفی است"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d دستگاه متصل</item>
- <item quantity="other">%1$d دستگاه متصل</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{هیچ دستگاهی متصل نیست}=1{یک دستگاه متصل است}one{# دستگاه متصل است}other{# دستگاه متصل است}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"زمان بیشتر."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"زمان کمتر."</string>
<string name="cancel" msgid="5665114069455378395">"لغو"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 4103a9f..3909b31 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ei rekisteröity"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ei käytettävissä"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-osoite satunnaistetaan"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d laitetta yhdistettynä</item>
- <item quantity="one">%1$d laite yhdistettynä</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Enemmän aikaa"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Vähemmän aikaa"</string>
<string name="cancel" msgid="5665114069455378395">"Peru"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index ab36e11..dd75461 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non enregistré"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Non accessible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Les adresses MAC sont randomisées"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d appareil connecté</item>
- <item quantity="other">%1$d appareils connectés</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Moins longtemps."</string>
<string name="cancel" msgid="5665114069455378395">"Annuler"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 35f2fce..0079b45 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non enregistré"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Non disponible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"La sélection des adresses MAC est aléatoire"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d appareil connecté</item>
- <item quantity="other">%1$d appareils connectés</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Moins longtemps."</string>
<string name="cancel" msgid="5665114069455378395">"Annuler"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 8d23864..4daa939 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non rexistrado"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Non dispoñible"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"O enderezo MAC é aleatorio"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d dispositivos conectados</item>
- <item quantity="one">%1$d dispositivo conectado</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Máis tempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index ac38a64..f9e1b9f 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"રજિસ્ટર કરેલ નથી"</string>
<string name="status_unavailable" msgid="5279036186589861608">"અનુપલબ્ધ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MACને રેન્ડમ કરેલ છે"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d ડિવાઇસ કનેક્ટ કર્યું</item>
- <item quantity="other">%1$d ડિવાઇસ કનેક્ટ કર્યા</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{કોઈ ડિવાઇસ કનેક્ટેડ નથી}=1{1 ડિવાઇસ કનેક્ટેડ છે}one{# ડિવાઇસ કનેક્ટેડ છે}other{# ડિવાઇસ કનેક્ટેડ છે}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"વધુ સમય."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ઓછો સમય."</string>
<string name="cancel" msgid="5665114069455378395">"રદ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 5c008f0..8bc4119 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"रजिस्टर नहीं है"</string>
<string name="status_unavailable" msgid="5279036186589861608">"मौजूद नहीं है"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"एमएसी पता रैंडम पर सेट है"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d डिवाइस जुड़ा है</item>
- <item quantity="other">%1$d डिवाइस जुड़े हैं</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ज़्यादा समय."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कम समय."</string>
<string name="cancel" msgid="5665114069455378395">"रद्द करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index d681a1e..ace20cc 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -526,11 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nije registrirano"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nije dostupno"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC adresa određena je nasumično"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Povezan je %1$d uređaj</item>
- <item quantity="few">Povezana su %1$d uređaja</item>
- <item quantity="other">Povezano je %1$d uređaja</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Povezano je 0 uređaja}=1{Povezan je jedan uređaj}one{Povezan je # uređaj}few{Povezana su # uređaja}other{Povezano je # uređaja}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Više vremena."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Manje vremena."</string>
<string name="cancel" msgid="5665114069455378395">"Odustani"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 57894a0..a0e4216 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nem regisztrált"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nem érhető el"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"A MAC-cím generálása véletlenszerű."</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d eszköz csatlakozik</item>
- <item quantity="one">%1$d eszköz csatlakozik</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Több idő."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kevesebb idő."</string>
<string name="cancel" msgid="5665114069455378395">"Mégse"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index e6ed2b0..34c674b 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Գրանցված չէ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Անհասանելի"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC հասցեն պատահականորեն է փոխվում"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Միացված է %1$d սարք</item>
- <item quantity="other">Միացված է %1$d սարք</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Թեժ կետին միացված սարքեր չկան}=1{Թեժ կետին 1 սարք է միացված}one{Թեժ կետին # սարք է միացված}other{Թեժ կետին # սարք է միացված}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Ավելացնել ժամանակը:"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Պակասեցնել ժամանակը:"</string>
<string name="cancel" msgid="5665114069455378395">"Չեղարկել"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 6fbab2b..2748a50 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Tidak terdaftar"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Tidak tersedia"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC diacak"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d perangkat terhubung</item>
- <item quantity="one">%1$d perangkat terhubung</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Lebih lama."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Lebih cepat."</string>
<string name="cancel" msgid="5665114069455378395">"Batal"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index fa11dfb..eeaf89b 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ekki skráð"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ekki tiltækt"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-vistfang er valið af handahófi"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d tæki tengt</item>
- <item quantity="other">%1$d tæki tengd</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meiri tími."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minni tími."</string>
<string name="cancel" msgid="5665114069455378395">"Hætta við"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 0db0448..e74ac154 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Non registrato"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Non disponibile"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Selezione casuale dell\'indirizzo MAC"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d devices connected</item>
- <item quantity="other">%1$d dispositivi connessi</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Più tempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Meno tempo."</string>
<string name="cancel" msgid="5665114069455378395">"Annulla"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 7ac5919..5da910b 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"לא רשום"</string>
<string name="status_unavailable" msgid="5279036186589861608">"לא זמין"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"כתובת ה-MAC אקראית"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="two">%1$d מכשירים מחוברים</item>
- <item quantity="many">%1$d מכשירים מחוברים</item>
- <item quantity="other">%1$d מכשירים מחוברים</item>
- <item quantity="one">מכשיר אחד מחובר</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"יותר זמן."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"פחות זמן."</string>
<string name="cancel" msgid="5665114069455378395">"ביטול"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index d82ca5d..c104b03 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未登録"</string>
<string name="status_unavailable" msgid="5279036186589861608">"不明"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC はランダムに設定されます"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d 台のデバイスが接続されています</item>
- <item quantity="one">%1$d 台のデバイスが接続されています</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{接続されているデバイスはありません}=1{1 台のデバイスが接続されています}other{# 台のデバイスが接続されています}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"長くします。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"短くします。"</string>
<string name="cancel" msgid="5665114069455378395">"キャンセル"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 02c1084..6c3e68a4 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"არარეგისტრირებული"</string>
<string name="status_unavailable" msgid="5279036186589861608">"მიუწვდომელია"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-ის მიმდევრობა არეულია"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">დაკავშირებულია %1$d მოწყობილობა</item>
- <item quantity="one">დაკავშირებულია %1$d მოწყობილობა</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"მეტი დრო."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ნაკლები დრო."</string>
<string name="cancel" msgid="5665114069455378395">"გაუქმება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 6c55b62..2b700b5 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Тіркелмеген"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Қолжетімсіз"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC еркін таңдауға қойылды"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d құрылғы қосылды</item>
- <item quantity="one">%1$d құрылғы қосылды</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Көбірек уақыт."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Азырақ уақыт."</string>
<string name="cancel" msgid="5665114069455378395">"Бас тарту"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 58a2c7c..c8af0df 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"មិនបានចុះឈ្មោះ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"មិនមាន"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ត្រូវបានជ្រើសរើសដោយចៃដន្យ"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">ឧបករណ៍ %1$d បានភ្ជាប់</item>
- <item quantity="one">ឧបករណ៍ %1$d បានភ្ជាប់</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"រយៈពេលច្រើនជាង។"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"រយៈពេលតិចជាង។"</string>
<string name="cancel" msgid="5665114069455378395">"បោះបង់"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 91e76e2..eeff165 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ನೋಂದಾಯಿಸಲಾಗಿಲ್ಲ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ಯಾದೃಚ್ಛಿಕವಾಗಿದೆ"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d ಸಾಧನಗಳನ್ನು ಸಂಪರ್ಕಿಸಲಾಗಿದೆ</item>
- <item quantity="other">%1$d ಸಾಧನಗಳನ್ನು ಸಂಪರ್ಕಿಸಲಾಗಿದೆ</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}=1{1 ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}one{# ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}other{# ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ಹೆಚ್ಚು ಸಮಯ."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ಕಡಿಮೆ ಸಮಯ."</string>
<string name="cancel" msgid="5665114069455378395">"ರದ್ದುಮಾಡಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index d470560..15bf7bc 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"등록되지 않음"</string>
<string name="status_unavailable" msgid="5279036186589861608">"사용할 수 없음"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC가 임의 선택됨"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">기기 %1$d개 연결됨</item>
- <item quantity="one">기기 %1$d개 연결됨</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"시간 늘리기"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"시간 줄이기"</string>
<string name="cancel" msgid="5665114069455378395">"취소"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e38bb51a..86f75eb 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Катталган эмес"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Жеткиликсиз"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC дарегин кокустан тандоо иштетилген"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d түзмөк туташып турат</item>
- <item quantity="one">%1$d түзмөк туташып турат</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 түзмөк туташып турат}=1{1 түзмөк туташып турат}other{# түзмөк туташып турат}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Көбүрөөк убакыт."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Азыраак убакыт."</string>
<string name="cancel" msgid="5665114069455378395">"Жок"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index cc02292..6133455 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ບໍ່ໄດ້ລົງທະບຽນ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ບໍ່ມີຂໍ້ມູນ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">ເຊື່ອມຕໍ່ %1$d ອຸປະກອນແລ້ວ</item>
- <item quantity="one">ເຊື່ອມຕໍ່ %1$d ອຸປະກອນແລ້ວ</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ເພີ່ມເວລາ."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ຫຼຸດເວລາ."</string>
<string name="cancel" msgid="5665114069455378395">"ຍົກເລີກ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index fb645a0..d089a8c 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neužregistruota"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Užimta"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC parinktas atsitiktine tvarka"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Prijungtas %1$d įrenginys</item>
- <item quantity="few">Prijungti %1$d įrenginiai</item>
- <item quantity="many">Prijungta %1$d įrenginio</item>
- <item quantity="other">Prijungta %1$d įrenginių</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daugiau laiko."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mažiau laiko."</string>
<string name="cancel" msgid="5665114069455378395">"Atšaukti"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 694ff0a..a28e8ab 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -526,11 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Nav reģistrēts"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nepieejams"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ir atlasīts nejaušā secībā"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="zero">Pievienotas %1$d ierīces</item>
- <item quantity="one">Pievienota %1$d ierīce</item>
- <item quantity="other">Pievienotas %1$d ierīces</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Vairāk laika."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mazāk laika."</string>
<string name="cancel" msgid="5665114069455378395">"Atcelt"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 9d412fc..d02e341 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не е регистриран"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Недостапно"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-адресата е рандомизирана"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Поврзан е %1$d уред</item>
- <item quantity="other">Поврзани се %1$d уреди</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 поврзани уреди}=1{1 поврзан уред}one{# поврзан уред}other{# поврзани уреди}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Повеќе време."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Помалку време."</string>
<string name="cancel" msgid="5665114069455378395">"Откажи"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 82df16c0..c952b25 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"രജിസ്റ്റർ ചെയ്തിട്ടില്ല"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ലഭ്യമല്ല"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC യാദൃച്ഛികമാക്കിയിരിക്കുന്നു"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്തു</item>
- <item quantity="one">%1$d ഉപകരണം കണക്റ്റ് ചെയ്തു</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ഉപകരണം കണക്റ്റ് ചെയ്തു}=1{1 ഉപകരണം കണക്റ്റ് ചെയ്തു}other{# ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്തു}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"കൂടുതൽ സമയം."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"കുറഞ്ഞ സമയം."</string>
<string name="cancel" msgid="5665114069455378395">"റദ്ദാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 027b722..dd5946b 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Бүртгээгүй"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Байхгүй"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC хаягийг үүсгэсэн"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d төхөөрөмж холбосон</item>
- <item quantity="one">%1$d төхөөрөмж холбосон</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 төхөөрөмж холбогдсон}=1{1 төхөөрөмж холбогдсон}other{# төхөөрөмж холбогдсон}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Их хугацаа."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Бага хугацаа."</string>
<string name="cancel" msgid="5665114069455378395">"Цуцлах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 176d764..fdbd313 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"नोंदवलेले नाही"</string>
<string name="status_unavailable" msgid="5279036186589861608">"उपलब्ध नाही"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC रँडमाइझ केला आहे"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d डिव्हाइस कनेक्ट केली आहेत</item>
- <item quantity="one">%1$d डिव्हाइस कनेक्ट केले आहे</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{एक डिव्हाइस कनेक्ट केले}other{# डिव्हाइस कनेक्ट केली}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"जास्त वेळ."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कमी वेळ."</string>
<string name="cancel" msgid="5665114069455378395">"रद्द करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 6bf15fe..f3be25f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Tidak didaftarkan"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Tidak tersedia"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC dirawakkan"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d peranti disambungkan</item>
- <item quantity="one">%1$d peranti disambungkan</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 peranti disambungkan}=1{1 peranti disambungkan}other{# peranti disambungkan}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Lagi masa."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kurang masa."</string>
<string name="cancel" msgid="5665114069455378395">"Batal"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 13fc6ad..7fbfd60 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"မှတ်ပုံတင်မထားပါ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"မရရှိနိုင်ပါ။"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ကို ကျပန်းပေးထားသည်"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">စက် %1$d ခု ချိတ်ဆက်ထားသည်</item>
- <item quantity="one">စက် %1$d ခု ချိတ်ဆက်ထားသည်</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"အချိန်တိုးရန်။"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"အချိန်လျှော့ရန်။"</string>
<string name="cancel" msgid="5665114069455378395">"မလုပ်တော့"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index c2e449b..5b999ba 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ikke registrert"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ikke tilgjengelig"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC velges tilfeldig"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d enheter er tilkoblet</item>
- <item quantity="one">%1$d enhet er tilkoblet</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mer tid."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mindre tid."</string>
<string name="cancel" msgid="5665114069455378395">"Avbryt"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 85860f0..ea0da04 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"दर्ता नगरिएको"</string>
<string name="status_unavailable" msgid="5279036186589861608">"अनुपलब्ध"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC क्रमरहित छ"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d डिभाइस कनेक्ट गरिएको छ</item>
- <item quantity="one">%1$d यन्त्र जडान गरियो</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"थप समय।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"कम समय।"</string>
<string name="cancel" msgid="5665114069455378395">"रद्द गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2ffa559..957386a 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Niet geregistreerd"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Niet beschikbaar"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adres is willekeurig"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d apparaten verbonden</item>
- <item quantity="one">%1$d apparaat verbonden</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Meer tijd."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Minder tijd."</string>
<string name="cancel" msgid="5665114069455378395">"Annuleren"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index e2fee13..5b15222 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ପଞ୍ଜିକୃତ ହୋଇନାହିଁ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MACର ଠିକଣା ରାଣ୍ଡମ୍ ଭାବେ ସେଟ୍ କରାଯାଇଛି"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$dଟି ଡିଭାଇସ୍ ସଂଯୁକ୍ତ ହୋଇଛି</item>
- <item quantity="one">%1$dଟି ଡିଭାଇସ୍ ସଂଯୁକ୍ତ ହୋଇଛି</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ଅଧିକ ସମୟ।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"କମ୍ ସମୟ।"</string>
<string name="cancel" msgid="5665114069455378395">"ବାତିଲ୍"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 11cd481..d044c04 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ਰਜਿਸਟਰ ਨਹੀਂ ਕੀਤੀ ਗਈ"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ਅਣਉਪਲਬਧ"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC ਬੇਤਰਤੀਬਾ ਹੈ"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ</item>
- <item quantity="other">%1$d ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤੇ ਗਏ</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ਹੋਰ ਸਮਾਂ।"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"ਘੱਟ ਸਮਾਂ।"</string>
<string name="cancel" msgid="5665114069455378395">"ਰੱਦ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 0c670bc..44edc09 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Niezarejestrowane"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Niedostępny"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adres MAC jest randomizowany"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="few">%1$d urządzenia podłączone</item>
- <item quantity="many">%1$d urządzeń podłączonych</item>
- <item quantity="other">%1$d urządzenia podłączonego</item>
- <item quantity="one">%1$d urządzenie podłączone</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Więcej czasu."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mniej czasu."</string>
<string name="cancel" msgid="5665114069455378395">"Anuluj"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 10568a1..7d7635c 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registrado"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Não disponível"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é randomizado"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d dispositivo conectado</item>
- <item quantity="other">%1$d dispositivos conectados</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 dispositivo conectado}=1{1 dispositivo conectado}one{# dispositivo conectado}other{# dispositivos conectados}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 5458279..9cccaab 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registado"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Indisponível"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é aleatório."</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d dispositivo ligado</item>
- <item quantity="other">%1$d dispositivos ligados</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 10568a1..7d7635c 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Não registrado"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Não disponível"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"O MAC é randomizado"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d dispositivo conectado</item>
- <item quantity="other">%1$d dispositivos conectados</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 dispositivo conectado}=1{1 dispositivo conectado}one{# dispositivo conectado}other{# dispositivos conectados}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mais tempo."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Menos tempo."</string>
<string name="cancel" msgid="5665114069455378395">"Cancelar"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 00fc8ff..3429b02 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -526,11 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neînregistrat"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Indisponibilă"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC este aleatoriu"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="few">%1$d dispozitive conectate</item>
- <item quantity="other">%1$d de dispozitive conectate</item>
- <item quantity="one">%1$d dispozitiv conectat</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Niciun dispozitiv conectat}=1{Un dispozitiv conectat}few{# dispozitive conectate}other{# de dispozitive conectate}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Mai mult timp."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Mai puțin timp."</string>
<string name="cancel" msgid="5665114069455378395">"Anulați"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 15e3456..2ecbcb7 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зарегистрирован"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Случайный MAC-адрес"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Подключено %1$d устройство</item>
- <item quantity="few">Подключено %1$d устройства</item>
- <item quantity="many">Подключено %1$d устройств</item>
- <item quantity="other">Подключено %1$d устройства</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Увеличить продолжительность"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Уменьшить продолжительность"</string>
<string name="cancel" msgid="5665114069455378395">"Отмена"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index d56dd34..8b8a1fb 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ලියාපදිංචි වී නැත"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ලබාගත නොහැක"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC සසම්භාවී වේ"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">උපාංග %1$dක් සම්බන්ධ කරන ලදී</item>
- <item quantity="other">උපාංග %1$dක් සම්බන්ධ කරන ලදී</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"වේලාව වැඩියෙන්."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"වේලාව අඩුවෙන්."</string>
<string name="cancel" msgid="5665114069455378395">"අවලංගු කරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index e7ea915..b088caf 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Neregistrované"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nie je k dispozícii"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC je náhodná"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="few">%1$d pripojené zariadenia</item>
- <item quantity="many">%1$d pripojeného zariadenia</item>
- <item quantity="other">%1$d pripojených zariadení</item>
- <item quantity="one">%1$d pripojené zariadenie</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Dlhší čas."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kratší čas."</string>
<string name="cancel" msgid="5665114069455378395">"Zrušiť"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 95a6fb9..c43addb 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -526,12 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ni registrirana"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ni na voljo"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Naslov MAC je naključno izbran"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Povezana je %1$d naprava</item>
- <item quantity="two">Povezani sta %1$d napravi</item>
- <item quantity="few">Povezane so %1$d naprave</item>
- <item quantity="other">Povezanih je %1$d naprav</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 naprav ni povezanih}=1{1 naprava je povezana}one{# naprava je povezana}two{# napravi sta povezani}few{# naprave so povezane}other{# naprav je povezanih}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daljši čas."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Krajši čas."</string>
<string name="cancel" msgid="5665114069455378395">"Prekliči"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 8524164..b45e653 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Paregjistruar"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Nuk ofrohet"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Adresa MAC është e rastësishme"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d pajisje të lidhura</item>
- <item quantity="one">%1$d pajisje e lidhur</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Më shumë kohë."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Më pak kohë."</string>
<string name="cancel" msgid="5665114069455378395">"Anulo"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b19198a..2b357b0 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -526,11 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Није регистрован"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC адреса је насумично изабрана"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Повезан је %1$d уређај</item>
- <item quantity="few">Повезана су %1$d уређаја</item>
- <item quantity="other">Повезано је %1$d уређаја</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 уређаја је повезано}=1{1 уређај је повезан}one{# уређај је повезан}few{# уређаја су повезана}other{# уређаја је повезано}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Више времена."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Мање времена."</string>
<string name="cancel" msgid="5665114069455378395">"Откажи"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 59cde86..c26c1fe 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Ej registrerad"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Inte tillgängligt"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC-adressen slumpgenereras"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d enheter är anslutna</item>
- <item quantity="one">%1$d enhet är ansluten</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Längre tid."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kortare tid."</string>
<string name="cancel" msgid="5665114069455378395">"Avbryt"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 64c2e07..239bb28 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Haijasajiliwa"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Hamna"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Imechagua anwani ya MAC kwa nasibu"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">Vifaa %1$d vimeunganishwa</item>
- <item quantity="one">Kifaa %1$d kimeunganishwa</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{Hakuna kifaa kimeunganishwa}=1{Kifaa 1 kimeunganishwa}other{Vifaa # vimeunganishwa}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Muda zaidi."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Muda kidogo."</string>
<string name="cancel" msgid="5665114069455378395">"Ghairi"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index ccef6b9..cf9fb97 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"பதிவு செய்யப்படவில்லை"</string>
<string name="status_unavailable" msgid="5279036186589861608">"கிடைக்கவில்லை"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC முகவரி சீரற்றுள்ளது"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d சாதனங்கள் இணைக்கப்பட்டன</item>
- <item quantity="one">%1$d சாதனம் இணைக்கப்பட்டது</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 சாதனம் இணைக்கப்பட்டது}=1{1 சாதனம் இணைக்கப்பட்டது}other{# சாதனங்கள் இணைக்கப்பட்டன}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"நேரத்தை அதிகரிக்கும்."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"நேரத்தைக் குறைக்கும்."</string>
<string name="cancel" msgid="5665114069455378395">"ரத்துசெய்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 4013620..2b0e750 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"నమోదు కాలేదు"</string>
<string name="status_unavailable" msgid="5279036186589861608">"అందుబాటులో లేదు"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC యాదృచ్ఛికంగా ఉంది"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d పరికరాలు కనెక్ట్ చేయబడ్డాయి</item>
- <item quantity="one">%1$d పరికరం కనెక్ట్ చేయబడింది</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 పరికరం కనెక్ట్ చేయబడింది}=1{1 పరికరం కనెక్ట్ చేయబడింది}other{# పరికరాలు కనెక్ట్ చేయబడ్డాయి}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ఎక్కువ సమయం."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"తక్కువ సమయం."</string>
<string name="cancel" msgid="5665114069455378395">"రద్దు చేయి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index c42295f..358431b 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"ไม่ได้ลงทะเบียน"</string>
<string name="status_unavailable" msgid="5279036186589861608">"ไม่มี"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC เป็นแบบสุ่ม"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">มีอุปกรณ์ที่เชื่อมต่อ %1$d เครื่อง</item>
- <item quantity="one">มีอุปกรณ์ที่เชื่อมต่อ %1$d เครื่อง</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{มีอุปกรณ์ที่เชื่อมต่ออยู่ 0 เครื่อง}=1{มีอุปกรณ์ที่เชื่อมต่ออยู่ 1 เครื่อง}other{มีอุปกรณ์ที่เชื่อมต่ออยู่ # เครื่อง}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"เวลามากขึ้น"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"เวลาน้อยลง"</string>
<string name="cancel" msgid="5665114069455378395">"ยกเลิก"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index ab36a55..2fcd334 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Hindi nakarehistro"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Hindi available"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Naka-randomize ang MAC"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d device ang nakakonekta</item>
- <item quantity="other">%1$d na device ang nakakonekta</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Dagdagan ang oras."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Bawasan ang oras."</string>
<string name="cancel" msgid="5665114069455378395">"Kanselahin"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index ce71496..5e0843b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Kaydettirilmedi"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Kullanılamıyor"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC rastgele yapıldı"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d cihaz bağlı</item>
- <item quantity="one">%1$d cihaz bağlı</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Daha uzun süre."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Daha kısa süre."</string>
<string name="cancel" msgid="5665114069455378395">"İptal"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index b7d370d2..f7ddb38 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -526,12 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Не зареєстровано"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Недоступно"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Для MAC-адреси вибрано функцію довільного вибору"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">Під’єднано %1$d пристрій</item>
- <item quantity="few">Під’єднано %1$d пристрої</item>
- <item quantity="many">Під’єднано %1$d пристроїв</item>
- <item quantity="other">Під’єднано %1$d пристрою</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Більше часу."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Менше часу."</string>
<string name="cancel" msgid="5665114069455378395">"Скасувати"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index c3f4d56..5268d53 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"رجسٹر نہیں ہے"</string>
<string name="status_unavailable" msgid="5279036186589861608">"غیر دستیاب"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC پتہ رینڈم ہے"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d آلات منسلک ہیں</item>
- <item quantity="one">%1$d آلہ منسلک ہے</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 آلہ منسلک ہے}=1{1 آلہ منسلک ہے}other{# آلات منسلک ہیں}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"زیادہ وقت۔"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"کم وقت۔"</string>
<string name="cancel" msgid="5665114069455378395">"منسوخ کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index aa3d8826..845a966 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -526,10 +526,7 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Registratsiya qilinmagan"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Mavjud emas"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Tasodifiy MAC manzil"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d ta qurilma ulangan</item>
- <item quantity="one">%1$d ta qurilma ulangan</item>
- </plurals>
+ <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 ta qurilma ulangan}=1{1 ta qurilma ulangan}other{# ta qurilma ulangan}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Ko‘proq vaqt."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Kamroq vaqt."</string>
<string name="cancel" msgid="5665114069455378395">"Bekor qilish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7291d06..7046250 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Chưa được đăng ký"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Không có"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Địa chỉ MAC được gán ngẫu nhiên"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d thiết bị đã kết nối</item>
- <item quantity="one">%1$d thiết bị đã kết nối</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Nhiều thời gian hơn."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Ít thời gian hơn."</string>
<string name="cancel" msgid="5665114069455378395">"Hủy"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 3f1b9ae..caa598d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未注册"</string>
<string name="status_unavailable" msgid="5279036186589861608">"无法获取"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 已随机化"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">已连接 %1$d 个设备</item>
- <item quantity="one">已连接 %1$d 个设备</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加时间。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"减少时间。"</string>
<string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index c91a13f..3b1833a 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未註冊"</string>
<string name="status_unavailable" msgid="5279036186589861608">"無法使用"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 位址已隨機產生"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d 部已連線的裝置</item>
- <item quantity="one">%1$d 部已連線的裝置</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加時間。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"減少時間。"</string>
<string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 8b469ea..70565bf 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"未註冊"</string>
<string name="status_unavailable" msgid="5279036186589861608">"無法取得"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC 位址已隨機化"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="other">%1$d 個已連線的裝置</item>
- <item quantity="one">%1$d 個已連線的裝置</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"增加時間。"</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"減少時間。"</string>
<string name="cancel" msgid="5665114069455378395">"取消"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 9eac12c..8c6496e 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -526,10 +526,8 @@
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Akubhalisiwe"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Ayitholakali"</string>
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"I-MAC ayihleliwe"</string>
- <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
- <item quantity="one">%1$d amadivayisi axhunyiwe</item>
- <item quantity="other">%1$d amadivayisi axhunyiwe</item>
- </plurals>
+ <!-- no translation found for wifi_tether_connected_summary (5282919920463340158) -->
+ <skip />
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Isikhathi esiningi."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Isikhathi esincane."</string>
<string name="cancel" msgid="5665114069455378395">"Khansela"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 290055c..1c9d9cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -504,6 +504,17 @@
}
/**
+ * Get identity address from remote device
+ * @return {@link BluetoothDevice#getIdentityAddress()} if
+ * {@link BluetoothDevice#getIdentityAddress()} is not null otherwise return
+ * {@link BluetoothDevice#getAddress()}
+ */
+ public String getIdentityAddress() {
+ final String identityAddress = mDevice.getIdentityAddress();
+ return TextUtils.isEmpty(identityAddress) ? getAddress() : identityAddress;
+ }
+
+ /**
* Get name from remote device
* @return {@link BluetoothDevice#getAlias()} if
* {@link BluetoothDevice#getAlias()} is not null otherwise return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
rename to packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index bec5fc8..afd3626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.settingslib.devicestate;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 7168f3c..46e31ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -116,6 +116,7 @@
private final boolean mDreamsEnabledByDefault;
private final boolean mDreamsActivatedOnSleepByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
+ private final Set<ComponentName> mDisabledDreams;
private final Set<Integer> mSupportedComplications;
private final Set<Integer> mDefaultEnabledComplications;
@@ -143,6 +144,10 @@
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
mDreamPreviewDefault = resources.getDrawable(
com.android.internal.R.drawable.default_dream_preview);
+ mDisabledDreams = Arrays.stream(resources.getStringArray(
+ com.android.internal.R.array.config_disabledDreamComponents))
+ .map(ComponentName::unflattenFromString)
+ .collect(Collectors.toSet());
mSupportedComplications =
Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications))
@@ -166,14 +171,16 @@
PackageManager.GET_META_DATA);
List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
for (ResolveInfo resolveInfo : resolveInfos) {
- if (resolveInfo.serviceInfo == null) {
+ final ComponentName componentName = getDreamComponentName(resolveInfo);
+ if (componentName == null || mDisabledDreams.contains(componentName)) {
continue;
}
+
DreamInfo dreamInfo = new DreamInfo();
dreamInfo.caption = resolveInfo.loadLabel(pm);
dreamInfo.icon = resolveInfo.loadIcon(pm);
dreamInfo.description = getDescription(resolveInfo, pm);
- dreamInfo.componentName = getDreamComponentName(resolveInfo);
+ dreamInfo.componentName = componentName;
dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo);
diff --git a/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml b/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml
new file mode 100644
index 0000000..61b9b3b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/layout/collapsing_test_layout.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:id="@+id/id_collapsing_test"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/text_hello_world"
+ android:text="Hello World!"/>
+
+</com.android.settingslib.collapsingtoolbar.widget.CollapsingCoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
new file mode 100644
index 0000000..06343f5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.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.settingslib.collapsingtoolbar.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link CollapsingCoordinatorLayout}. */
+@RunWith(RobolectricTestRunner.class)
+public class CollapsingCoordinatorLayoutTest {
+ private static final String TEXT_HELLO_WORLD = "Hello World!";
+ private static final String TEST_TITLE = "RENO NAKAMURA";
+
+ private TestActivity mActivity;
+
+ @Before
+ public void setUp() {
+ mActivity = Robolectric.buildActivity(TestActivity.class).create().get();
+ }
+
+ @Test
+ public void onCreate_childViewsNumberShouldBeTwo() {
+ CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+
+ assertThat(layout.getChildCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
+ CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+ View contentFrameView = layout.findViewById(R.id.content_frame);
+
+ TextView textView = contentFrameView.findViewById(R.id.text_hello_world);
+
+ assertThat(textView).isNotNull();
+ assertThat(textView.getText().toString()).isEqualTo(TEXT_HELLO_WORLD);
+ }
+
+ @Test
+ public void initSettingsStyleToolBar_assignedTitle() {
+ CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
+
+ layout.initSettingsStyleToolBar(mActivity, TEST_TITLE);
+
+ assertThat(layout.getCollapsingToolbarLayout().getTitle().toString()).isEqualTo(TEST_TITLE);
+ }
+
+ public static class TestActivity extends Activity {
+ private CollapsingCoordinatorLayout mCollapsingCoordinatorLayout;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(android.R.style.Theme_Light_NoTitleBar);
+ setContentView(R.layout.collapsing_test_layout);
+ mCollapsingCoordinatorLayout = findViewById(R.id.id_collapsing_test);
+ }
+
+ public CollapsingCoordinatorLayout getCollapsingCoordinatorLayout() {
+ return mCollapsingCoordinatorLayout;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
index 5b0f659..2f3da83 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.net;
+import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
@@ -83,7 +84,6 @@
mLoader.recordUsage(start, end);
- verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, uid);
verify(mNetworkStatsManager).queryDetailsForUidTagState(
mNetworkTemplate, start, end, uid, TAG_NONE, STATE_FOREGROUND);
}
@@ -116,9 +116,12 @@
mLoader.recordUsage(start, end);
- verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 1);
- verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 2);
- verify(mNetworkStatsManager).queryDetailsForUid(mNetworkTemplate, start, end, 3);
+ verify(mNetworkStatsManager).queryDetailsForUidTagState(mNetworkTemplate, start, end, 1,
+ TAG_NONE, STATE_ALL);
+ verify(mNetworkStatsManager).queryDetailsForUidTagState(mNetworkTemplate, start, end, 2,
+ TAG_NONE, STATE_ALL);
+ verify(mNetworkStatsManager).queryDetailsForUidTagState(mNetworkTemplate, start, end, 3,
+ TAG_NONE, STATE_ALL);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 927f11f..51870e2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3624,7 +3624,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 208;
+ private static final int SETTINGS_VERSION = 209;
private final int mUserId;
@@ -5497,6 +5497,20 @@
currentVersion = 208;
}
+ if (currentVersion == 208) {
+ // Version 208: Enable enforcement of
+ // android.Manifest.permission#POST_NOTIFICATIONS in order for applications
+ // to post notifications.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ secureSettings.insertSettingLocked(
+ Secure.NOTIFICATION_PERMISSION_ENABLED,
+ /* enabled= */" 1",
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ currentVersion = 209;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c6fbfd8..ebff00c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -204,7 +204,6 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.CREATE_USERS" />
<uses-permission android:name="android.permission.QUERY_USERS" />
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
<uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" />
<uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
<uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
@@ -309,6 +308,7 @@
<uses-permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" />
<uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
@@ -336,6 +336,9 @@
<!-- Permission needed to run keyguard manager tests in CTS -->
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
+ <!-- Permission needed to add/remove weak escrow token in CTS tests -->
+ <uses-permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN" />
+
<!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests -->
<uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" />
@@ -526,6 +529,7 @@
<!-- Permission needed for CTS test - WifiManagerTest -->
<uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
<uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
+ <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
<!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
<uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
@@ -557,6 +561,9 @@
<!-- Permission required for CTS test - CtsGameManagerTestCases -->
<uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
+ <!-- Permission required for CTS test - CtsGameServiceTestCases -->
+ <uses-permission android:name="android.permission.SET_GAME_SERVICE" />
+
<!-- Permission required for CTS test - ClipboardManagerTest -->
<uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
@@ -625,6 +632,11 @@
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
<uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" />
+ <!-- Permissions required for CTS test - CtsVirtualDevicesTestCases -->
+ <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
+ <uses-permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
+
<!-- Permission required for CTS test - Notification test suite -->
<uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index c5a01a1..0b8bd97 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -72,6 +72,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Patterns;
+import android.util.PluralsMessageFormatter;
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.IWindowManager;
@@ -111,7 +112,9 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -943,9 +946,12 @@
}
setTakingScreenshot(true);
collapseNotificationBar();
- final String msg = mContext.getResources()
- .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
- mScreenshotDelaySec, mScreenshotDelaySec);
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", mScreenshotDelaySec);
+ final String msg = PluralsMessageFormatter.format(
+ mContext.getResources(),
+ arguments,
+ com.android.internal.R.string.bugreport_countdown);
Log.i(TAG, msg);
// Show a toast just once, otherwise it might be captured in the screenshot.
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 137a1fd..b1cfb11 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -107,6 +107,7 @@
"androidx.slice_slice-view",
"androidx.slice_slice-builders",
"androidx.arch.core_core-runtime",
+ "androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
@@ -209,6 +210,7 @@
"androidx.slice_slice-view",
"androidx.slice_slice-builders",
"androidx.arch.core_core-runtime",
+ "androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8e9e02a..d9db436 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -120,10 +120,11 @@
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
<!-- Countdown message shown after too many failed unlock attempts -->
- <plurals name="kg_too_many_failed_attempts_countdown">
- <item quantity="one">Try again in 1 second.</item>
- <item quantity="other">Try again in <xliff:g id="number">%d</xliff:g> seconds.</item>
- </plurals>
+ <string name="kg_too_many_failed_attempts_countdown">{count, plural,
+ =1 {Try again in # second.}
+ other {Try again in # seconds.}
+ }
+ </string>
<!-- Instructions for using the SIM PIN unlock screen -->
<string name="kg_sim_pin_instructions">Enter SIM PIN.</string>
<!-- Instructions for using the SIM PIN unlock screen when there's more than one SIM -->
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 7ffb3b2..2a3761e 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,6 +17,7 @@
<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:alpha="0"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
diff --git a/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml b/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml
new file mode 100644
index 0000000..328570b
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_dot_bottom_left_container"
+ android:layout_height="@dimen/status_bar_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|left"
+ android:paddingTop="@dimen/status_bar_padding_top"
+ android:visibility="invisible" >
+ <ImageView
+ android:id="@+id/privacy_dot"
+ android:contentDescription="@null"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|right"
+ android:src="@drawable/system_animation_ongoing_dot"
+ android:visibility="visible" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml b/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml
new file mode 100644
index 0000000..34b74f3
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_dot_bottom_right_container"
+ android:layout_height="@dimen/status_bar_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:paddingTop="@dimen/status_bar_padding_top"
+ android:visibility="invisible" >
+ <ImageView
+ android:id="@+id/privacy_dot"
+ android:contentDescription="@null"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|left"
+ android:src="@drawable/system_animation_ongoing_dot"
+ android:visibility="visible" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dot_top_left.xml b/packages/SystemUI/res/layout/privacy_dot_top_left.xml
new file mode 100644
index 0000000..ea6c886
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dot_top_left.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_dot_top_left_container"
+ android:layout_height="@dimen/status_bar_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="top|left"
+ android:paddingTop="@dimen/status_bar_padding_top"
+ android:visibility="invisible" >
+ <ImageView
+ android:id="@+id/privacy_dot"
+ android:contentDescription="@null"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|right"
+ android:src="@drawable/system_animation_ongoing_dot"
+ android:visibility="visible" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dot_top_right.xml b/packages/SystemUI/res/layout/privacy_dot_top_right.xml
new file mode 100644
index 0000000..bcda0da
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dot_top_right.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/privacy_dot_top_right_container"
+ android:layout_height="@dimen/status_bar_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="top|right"
+ android:paddingTop="@dimen/status_bar_padding_top"
+ android:visibility="invisible" >
+ <ImageView
+ android:id="@+id/privacy_dot"
+ android:contentDescription="@null"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical|left"
+ android:src="@drawable/system_animation_ongoing_dot"
+ android:visibility="visible" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index f91ab6f..b2857ab 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -27,22 +27,6 @@
android:tint="#ff000000"
android:src="@drawable/rounded_corner_bottom"/>
- <FrameLayout
- android:id="@+id/privacy_dot_left_container"
- android:layout_height="@dimen/status_bar_height"
- android:layout_width="wrap_content"
- android:paddingTop="@dimen/status_bar_padding_top"
- android:layout_gravity="left|bottom"
- android:visibility="invisible" >
- <ImageView
- android:id="@+id/privacy_dot"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|right"
- android:src="@drawable/system_animation_ongoing_dot"
- android:visibility="visible" />
- </FrameLayout>
-
<ImageView
android:id="@+id/right"
android:layout_width="12dp"
@@ -51,20 +35,4 @@
android:layout_gravity="right|bottom"
android:src="@drawable/rounded_corner_bottom"/>
- <FrameLayout
- android:id="@+id/privacy_dot_right_container"
- android:layout_height="@dimen/status_bar_height"
- android:layout_width="wrap_content"
- android:paddingTop="@dimen/status_bar_padding_top"
- android:layout_gravity="right|bottom"
- android:visibility="invisible" >
- <ImageView
- android:id="@+id/privacy_dot"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|left"
- android:src="@drawable/system_animation_ongoing_dot"
- android:visibility="visible" />
- </FrameLayout>
-
</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 819a9a4e9..9937c21 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -27,22 +27,6 @@
android:tint="#ff000000"
android:src="@drawable/rounded_corner_top"/>
- <FrameLayout
- android:id="@+id/privacy_dot_left_container"
- android:layout_height="@dimen/status_bar_height"
- android:layout_width="wrap_content"
- android:paddingTop="@dimen/status_bar_padding_top"
- android:layout_gravity="left|top"
- android:visibility="invisible" >
- <ImageView
- android:id="@+id/privacy_dot"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|right"
- android:src="@drawable/system_animation_ongoing_dot"
- android:visibility="visible" />
- </FrameLayout>
-
<ImageView
android:id="@+id/right"
android:layout_width="12dp"
@@ -51,20 +35,4 @@
android:layout_gravity="right|top"
android:src="@drawable/rounded_corner_top"/>
- <FrameLayout
- android:id="@+id/privacy_dot_right_container"
- android:layout_height="@dimen/status_bar_height"
- android:layout_width="wrap_content"
- android:paddingTop="@dimen/status_bar_padding_top"
- android:layout_gravity="right|top"
- android:visibility="invisible" >
- <ImageView
- android:id="@+id/privacy_dot"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|left"
- android:src="@drawable/system_animation_ongoing_dot"
- android:visibility="visible" />
- </FrameLayout>
-
</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 10b8ef4..4a5de4c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Tot sonsopkoms"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aan om <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Tot <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is gedeaktiveer"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is geaktiveer"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrole is nie beskikbaar nie"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programme wat op die agtergrond werk"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopieer"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Gekopieer"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 6fe6f53..572877a 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"ኤንኤፍሲ"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"ኤንኤፍሲ ተሰናክሏል"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"ኤንኤፍሲ ነቅቷል"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ለማየት ይክፈቱ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"የእርስዎን ካርዶች ማግኘት ላይ ችግር ነበር፣ እባክዎ ቆይተው እንደገና ይሞክሩ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"የገጽ መቆለፊያ ቅንብሮች"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR ኮድ"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"ለመቃኘት መታ ያድርጉ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"መቆጣጠሪያ አይገኝም"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ከበስተጀርባ የሚሠሩ መተግበሪያዎች"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"መቆሚያ"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"ቅዳ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"ተቀድቷል"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 5a0c7a1..89e9306 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -277,6 +277,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"حتى شروق الشمس"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"تفعيل الوضع في <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"حتى <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"تم إيقاف الاتصال القريب المدى"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"تم تفعيل الاتصال القريب المدى"</string>
@@ -461,10 +465,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"فتح القفل للاستخدام"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"حدثت مشكلة أثناء الحصول على البطاقات، يُرجى إعادة المحاولة لاحقًا."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"رمز الاستجابة السريعة"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"انقر للمسح ضوئيًا"</string>
<string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -817,9 +819,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"عليك الاقتراب لتشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"جارٍ تشغيل الموسيقى على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"عنصر التحكّم غير متوفّر"</string>
@@ -906,8 +914,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"التطبيقات التي يتم تشغيلها في الخلفية"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"إيقاف"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"نسخ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"تم النسخ."</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index bf35ad2..8ae383f 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"সূৰ্যোদয়লৈকে"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ত অন কৰক"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> পৰ্যন্ত"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC নিষ্ক্ৰিয় হৈ আছে"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম হৈ আছে"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যৱহাৰ কৰিবলৈ আনলক কৰক"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"আপোনাৰ কাৰ্ড লাভ কৰোঁতে এটা সমস্যা হৈছে, অনুগ্ৰহ কৰি পাছত পুনৰ চেষ্টা কৰক"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্ৰীনৰ ছেটিং"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"কিউআৰ ক\'ড"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"স্কেন কৰিবলৈ টিপক"</string>
<string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লেইন ম\'ড"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে’ কৰিবলৈ ওচৰলৈ যাওক"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰি থকা হৈছে"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"নিয়ন্ত্ৰণটো উপলব্ধ নহয়"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"নেপথ্যত চলি থকা এপ্"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ কৰক"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"প্ৰতিলিপি কৰক"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"প্ৰতিলিপি কৰা হ’ল"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index d491724..d266152 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Şəfəq vaxtına qədər"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Bu vaxt aktiv olur: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Bu vaxtadək: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC deaktiv edilib"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC aktiv edilib"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"İstifadə etmək üçün kiliddən çıxarın"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kartların əldə edilməsində problem oldu, sonra yenidən cəhd edin"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilid ekranı ayarları"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kodu"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Skanlamaq üçün toxunun"</string>
<string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Nəzarət əlçatan deyil"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arxa fonda işləyən tətbiqlər"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dayandırın"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopyalayın"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopyalandı"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 955096e..6bdbd79 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do izlaska sunca"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
@@ -797,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Pušta se na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -886,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije pokrenute u pozadini"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiraj"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopirano je"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 4d11111..2d5aa8c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Да ўсходу сонца"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Уключана ў <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Да <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC адключаны"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC уключаны"</string>
@@ -455,10 +459,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблакіраваць для выкарыстання"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Узнікла праблема з загрузкай вашых карт. Паўтарыце спробу пазней"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Налады экрана блакіроўкі"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Націсніце, каб адсканіраваць"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -805,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Каб прайграць мультымедыя на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", наблізьцеся да яе"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Кіраванне недаступнае"</string>
@@ -894,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Праграмы працуюць у фонавым рэжыме"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спыніць"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Капіраваць"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Скапіравана"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index aaa1f3a..ab7a38b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изгрев"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ще се включи в <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"КБП е деактивирана"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"КБП е активирана"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отключване с цел използване"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"При извличането на картите ви възникна проблем. Моля, опитайте отново по-късно"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки за заключения екран"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR код"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Докоснете за сканиране"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е налице"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, които се изпълняват на заден план"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Спиране"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Копиране"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Копирано"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index be290af..6e72cc4 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"সূর্যোদয় পর্যন্ত"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-এ চালু হবে"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> পর্যন্ত"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC অক্ষম করা আছে"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম করা আছে"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যবহার করতে আনলক করুন"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"আপনার কার্ড সংক্রান্ত তথ্য পেতে সমস্যা হয়েছে, পরে আবার চেষ্টা করুন"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্রিন সেটিংস"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR কোড"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"স্ক্যান করতে ট্যাপ করুন"</string>
<string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"কন্ট্রোল উপলভ্য নেই"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ব্যাকগ্রাউন্ডে অ্যাপ চলছে"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"বন্ধ করুন"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"কপি করুন"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"কপি করা হয়েছে"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index eb45708..3fd796d 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do svitanja"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
@@ -797,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -886,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije su aktivne u pozadini"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiraj"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopirano"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 06799a2..88db881c 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Fins a l\'alba"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Activat a les <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Fins a les <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"L\'NFC està desactivada"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"L\'NFC està activada"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloqueja per utilitzar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Hi ha hagut un problema en obtenir les teves targetes; torna-ho a provar més tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuració de la pantalla de bloqueig"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Codi QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toca per escanejar"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"El control no està disponible"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacions que s\'executen en segon pla"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Atura"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copia"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"S\'ha copiat"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 4b63cd8..dd1f289 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do svítání"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Zapnout v <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je vypnuto"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je zapnuto"</string>
@@ -456,8 +460,7 @@
<string name="wallet_error_generic" msgid="257704570182963611">"Při načítání karet došlo k problému, zkuste to později"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavení obrazovky uzamčení"</string>
<string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string>
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Klepnutím naskenujete kód"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string>
@@ -804,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Přehrává se na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládání není k dispozici"</string>
@@ -893,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikace běžící na pozadí"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Konec"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopírovat"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Zkopírováno"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ad92cad..8011a02 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Indtil solopgang"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Tænd kl. <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Indtil kl. <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er deaktiveret"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er aktiveret"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås op for at bruge"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Dine kort kunne ikke hentes. Prøv igen senere."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lås skærmindstillinger"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kode"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tryk for at scanne"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flyt enheden tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Styringselement ikke tilgængeligt"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, der kører i baggrunden"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiér"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopieret"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 8b51482..a7c1d2b 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Bis Sonnenaufgang"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"An um <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Bis <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ist deaktiviert"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ist aktiviert"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Zum Verwenden entsperren"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Beim Abrufen deiner Karten ist ein Fehler aufgetreten – bitte versuch es später noch einmal"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Einstellungen für den Sperrbildschirm"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-Code"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Zum Scannen tippen"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gehe für die Wiedergabe näher an <xliff:g id="DEVICENAME">%1$s</xliff:g> heran"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Wird auf <xliff:g id="DEVICENAME">%1$s</xliff:g> abgespielt"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Steuerelement nicht verfügbar"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps, die im Hintergrund ausgeführt werden"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Beenden"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopieren"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopiert"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 9e61f1a..86a035a 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Μέχρι την ανατολή"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ενεργοποίηση στις <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Έως <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Το NFC είναι απενεργοποιημένο"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Το NFC είναι ενεργοποιημένο"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ξεκλείδωμα για χρήση"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Παρουσιάστηκε πρόβλημα με τη λήψη των καρτών σας. Δοκιμάστε ξανά αργότερα"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ρυθμίσεις κλειδώματος οθόνης"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Κωδικός QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Πατήστε για σάρωση"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Μη διαθέσιμο στοιχείο ελέγχου"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Οι εφαρμογές βρίσκονται σε εξέλιξη στο παρασκήνιο"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Διακοπή"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Αντιγραφή"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Αντιγράφηκε"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 79c9ca2..b6e8b0f 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 605811d..206042e 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 79c9ca2..b6e8b0f 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 79c9ca2..b6e8b0f 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps running in the background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copy"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copied"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 3895548..b9db8f5 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -269,6 +269,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"On at bedtime"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Until bedtime ends"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -792,7 +794,14 @@
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
- <string name="media_transfer_playing" msgid="3760048096352107789">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index f748e9d..0414a18 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hasta el amanecer"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"A la(s) <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hasta la(s) <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"La tecnología NFC está inhabilitada"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"La tecnología NFC está habilitada"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocurrió un problema al obtener las tarjetas; vuelve a intentarlo más tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración de pantalla de bloqueo"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Presiona para escanear"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"El control no está disponible"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps en ejecución en segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Se copió"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 0494780..949b87c 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hasta el amanecer"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"A las <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hasta las <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"El NFC está desactivado"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"El NFC está activado"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Se ha producido un problema al obtener tus tarjetas. Inténtalo de nuevo más tarde."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ajustes de pantalla de bloqueo"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Toca para escanear"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para que se reproduzca en ese dispositivo"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control no disponible"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicaciones ejecutándose en segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Detener"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 15c10be..68b3699 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Kuni päikesetõusuni"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Sisse kell <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Kuni <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on keelatud"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on lubatud"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avage kasutamiseks"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Teie kaartide hankimisel ilmnes probleem, proovige hiljem uuesti"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukustuskuva seaded"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kood"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Skannimiseks puudutamine"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Teisaldage lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Juhtelement pole saadaval"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Taustal töötavad rakendused"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Peata"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopeeri"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopeeritud"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 454ad59..5c197a7 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Egunsentira arte"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Desaktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Desgaituta dago NFC"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Gaituta dago NFC"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desblokeatu erabiltzeko"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Arazo bat izan da txartelak eskuratzean. Saiatu berriro geroago."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Pantaila blokeatuaren ezarpenak"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kodea"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Sakatu eskaneatzeko"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profila"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gertura ezazu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> pantailan erreproduzitzen"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ez dago erabilgarri kontrolatzeko aukera"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Atzeko planoan exekutatzen ari diren aplikazioak"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Gelditu"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiatu"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopiatu da"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index c2c1ffb..6219088 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"تا طلوع آفتاب"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ساعت <xliff:g id="TIME">%s</xliff:g> روشن میشود"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"تا<xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC غیرفعال است"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC فعال است"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"برای استفاده، قفل را باز کنید"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"هنگام دریافت کارتها مشکلی پیش آمد، لطفاً بعداً دوباره امتحان کنید"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"تنظیمات صفحه قفل"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"رمزینه پاسخسریع"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"برای اسکن کردن، ضربه بزنید"</string>
<string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمیشنوید"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g> به دستگاه نزدیکتر شوید"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"کنترل دردسترس نیست"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"برنامههایی که در پسزمینه اجرا میشود"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"توقف"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"کپی کردن"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"کپی شد"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index cd3844f..4556dc9 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Auringonnousuun"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Päälle klo <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> asti"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on poistettu käytöstä"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on käytössä"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avaa lukitus ja käytä"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Korttien noutamisessa oli ongelma, yritä myöhemmin uudelleen"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukitusnäytön asetukset"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-koodi"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Skannaa napauttamalla"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> toistaa tämän"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ohjain ei ole käytettävissä"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Sovellukset jotka ovat käynnissä taustalla"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Lopeta"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopioi"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopioitu"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 440369d..bda7b05 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Jusqu\'à l\'aube"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Actif à <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Jusqu\'à <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"CCP"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"CCP désactivée"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"CCP activée"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Un problème est survenu lors de la récupération de vos cartes, veuillez réessayer plus tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Code QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Touchez pour numériser"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applications exécutées en arrière-plan"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copier"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copié"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 891e85c..ec78946 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Jusqu\'à l\'aube"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"À partir de <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Jusqu\'à <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC désactivée"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"La technologie NFC est activée"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Problème de récupération de vos cartes. Réessayez plus tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Code QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Appuyer pour scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour lire sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>…"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Commande indisponible"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Applis exécutées en arrière-plan"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Arrêter"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copier"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copié"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 70a3c68..73af2fe 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Ata o amencer"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Activación: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Utilizarase ata as: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"A opción NFC está desactivada"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"A opción NFC está activada"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Produciuse un problema ao obter as tarxetas. Téntao de novo máis tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración da pantalla de bloqueo"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tocar para escanear"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Achega o dispositivo para reproducir o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Estase reproducindo o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O control non está dispoñible"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicacións que se están executando en segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Deter"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiouse"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 5f262b5..3cd94b3 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"સૂર્યોદય સુધી"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> વાગ્યે ચાલુ"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> વાગ્યા સુધી"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC અક્ષમ કરેલ છે"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC સક્ષમ કરેલ છે"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ઉપયોગ કરવા માટે અનલૉક કરો"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"તમારા કાર્ડની માહિતી મેળવવામાં સમસ્યા આવી હતી, કૃપા કરીને થોડા સમય પછી ફરી પ્રયાસ કરો"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"લૉક સ્ક્રીનના સેટિંગ"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR કોડ"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"સ્કૅન કરવા માટે ટૅપ કરો"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"નિયંત્રણ ઉપલબ્ધ નથી"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"બૅકગ્રાઉન્ડમાં ચાલતી ઍપ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"રોકો"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"કૉપિ કરો"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"કૉપિ કરવામાં આવી"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 755c480..fd79377 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सुबह तक चालू रहेगी"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> पर चालू हाेगी"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> तक चालू रहेगी"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"एनएफ़सी"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC बंद है"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC चालू है"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"इस्तेमाल करने के लिए, डिवाइस अनलॉक करें"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"आपके कार्ड की जानकारी पाने में कोई समस्या हुई है. कृपया बाद में कोशिश करें"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन की सेटिंग"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"क्यूआर कोड"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"स्कैन करने के लिए टैप करें"</string>
<string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"हवाई जहाज़ मोड"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चलाने के लिए, अपने डिवाइस को उसके पास ले जाएं"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर चल रहा है"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"बैकग्राउंड में चल रहे ऐप्लिकेशन"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"बंद करें"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"कॉपी करें"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"कॉपी किया गया"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index a5be1e3..2fbaef3 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do izlaska sunca"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string>
@@ -452,10 +456,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da biste koristili"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pojavio se problem prilikom dohvaćanja kartica, pokušajte ponovo kasnije"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključanog zaslona"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kôd"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Dodirnite za skeniranje"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -799,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -888,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije koje se izvode u pozadini"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zaustavi"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiraj"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopirano"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 0275b72..2b9f3cc 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Napfelkeltéig"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Be: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Eddig: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Az NFC ki van kapcsolva"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Az NFC be van kapcsolva"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Oldja fel a használathoz"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Probléma merült fel a kártyák lekérésekor, próbálja újra később"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lezárási képernyő beállításai"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kód"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Koppintson a beolvasáshoz"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Menjen közelebb a következőn való lejátszáshoz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Nem hozzáférhető vezérlő"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Több alkalmazás is fut a háttérben"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Leállítás"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Másolás"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Másolva"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index c32b166..d08731f 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Մինչև լուսաբաց"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Կմիանա՝ <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Մինչև <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-ն անջատված է"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-ն միացված է"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ապակողպել՝ օգտագործելու համար"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Չհաջողվեց բեռնել քարտերը։ Նորից փորձեք։"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Կողպէկրանի կարգավորումներ"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR կոդ"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Հպեք՝ սկանավորելու համար"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Նվագարկվում է <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Կառավարման տարրը հասանելի չէ"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ֆոնային ռեժիմում աշխատող հավելվածներ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Դադարեցնել"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Պատճենել"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Պատճենվեց"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 24f8698..a6c2303 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Sampai pagi"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktif pada <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Sampai <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dinonaktifkan"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC diaktifkan"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Terjadi masalah saat mendapatkan kartu Anda, coba lagi nanti"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setelan layar kunci"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Kode QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Ketuk untuk memindai"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol tidak tersedia"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikasi berjalan di latar belakang"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Salin"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Disalin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index a7a43e6..026200c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Til sólarupprásar"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Virkt kl. <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Til <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Slökkt á NFC"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Kveikt á NFC"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Taktu úr lás til að nota"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Vandamál kom upp við að sækja kortin þín. Reyndu aftur síðar"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Stillingar fyrir læstan skjá"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kóði"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Ýttu til að skanna"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Spilast í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Stýring er ekki tiltæk"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Forrit keyra í bakgrunni"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stöðva"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Afrita"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Afritað"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 2f3d50f..120a6a5 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Fino all\'alba"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Attivazione alle <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Fino alle <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC non attiva"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC attiva"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Sblocca per usare"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Si è verificato un problema durante il recupero delle tue carte. Riprova più tardi."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Impostazioni schermata di blocco"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Codice QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Tocca per scansionare"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Il controllo non è disponibile"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"App in esecuzione in background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Interrompi"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copia"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiato"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 757ff77..2825d24 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"עד הזריחה"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"יתחיל בשעה <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"עד <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC מושבת"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC מופעל"</string>
@@ -455,10 +459,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"יש לבטל את הנעילה כדי להשתמש"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"הייתה בעיה בקבלת הכרטיסים שלך. כדאי לנסות שוב מאוחר יותר"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"הגדרות מסך הנעילה"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"קוד QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"צריך להקיש כדי לסרוק"</string>
<string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את ההתראה הבאה שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -805,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"צריך להתקרב כדי להפעיל מוזיקה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"ההפעלה הועברה למכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"הפקד לא זמין"</string>
@@ -894,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"אפליקציות שפועלות ברקע"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"עצירה"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"העתקה"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"הועתק"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index f219de3..1d0537c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"日の出まで"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>にオン"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>まで"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC は無効です"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC は有効です"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"コントロールを使用できません"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"バックグラウンドで実行中のアプリ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"コピー"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"コピーしました"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index b932c45..5ed24da 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"მზის ამოსვლამდე"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ჩაირთოს <xliff:g id="TIME">%s</xliff:g>-ზე"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>-მდე"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC გათიშულია"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ჩართულია"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედების გაუქმება"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"მიიტანეთ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"კონტროლი მიუწვდომელია"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ფონურად მომუშავე აპები"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"შეწყვეტა"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"კოპირება"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"კოპირებულია"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 1ac6c7e..62f1f75 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> дейін"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Пайдалану үшін құлыпты ашу"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Карталарыңыз алынбады, кейінірек қайталап көріңіз."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экран құлпының параметрлері"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR коды"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Сканерлеу үшін түртіңіз."</string>
<string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Басқару виджеті қолжетімсіз"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондық режимде жұмыс істеп тұрған қолданбалар"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Тоқтату"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Көшіру"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Көшірілді"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index bc69da9..1f64cf9 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"រហូតដល់ពេលថ្ងៃរះ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"បើកនៅម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"រហូតដល់ម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"បានបិទ NFC"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"បានបើក NFC"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"កំពុងចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"មិនអាចគ្រប់គ្រងបានទេ"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"កម្មវិធីដែលកំពុងដំណើរការនៅផ្ទៃខាងក្រោយ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ឈប់"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"ចម្លង"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"បានចម្លង"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index c388d82..2b80b8a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ಸೂರ್ಯೋದಯದವರೆಗೆ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ಸಮಯದಲ್ಲಿ"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ವರೆಗೂ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ನಿಷ್ಕ್ರಿಯಗೊಂಡಿದೆ"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ಸಕ್ರಿಯಗೊಂಡಿದೆ"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ಬಳಸಲು ಅನ್ಲಾಕ್ ಮಾಡಿ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ನಿಮ್ಮ ಕಾರ್ಡ್ಗಳನ್ನು ಪಡೆಯುವಾಗ ಸಮಸ್ಯೆ ಉಂಟಾಗಿದೆ, ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ಲಾಕ್ ಸ್ಕ್ರ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR ಕೋಡ್"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"ಸ್ಕ್ಯಾನ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುತ್ತಿರುವ ಆ್ಯಪ್ಗಳು"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ನಿಲ್ಲಿಸಿ"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"ನಕಲಿಸಿ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"ನಕಲಿಸಲಾಗಿದೆ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 16b5447..dbe0290 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"일출까지"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>에 켜짐"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>까지"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 사용 중지됨"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 사용 설정됨"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"잠금 해제하여 사용"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"카드를 가져오는 중에 문제가 발생했습니다. 나중에 다시 시도해 보세요."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"잠금 화면 설정"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 코드"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"탭하여 스캔"</string>
<string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"컨트롤을 사용할 수 없음"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"백그라운드에서 실행 중인 앱"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"중지"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"복사"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"복사됨"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 31c8522..f959935 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн чыкканга чейин"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Саат <xliff:g id="TIME">%s</xliff:g> күйөт"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> чейин"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өчүрүлгөн"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC иштетилген"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Колдонуу үчүн кулпусун ачыңыз"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Кыйытмаларды алууда ката кетти. Бир аздан кийин кайталап көрүңүз."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Кулпуланган экран жөндөөлөрү"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR коду"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Скандоо үчүн таптап коюңуз"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакыныраак жылдырыңыз"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Башкара албайсыз"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Фондо иштеп жаткан колдонмолор"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Токтотуу"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Көчүрүү"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Көчүрүлдү"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index f3884bc..8e2641f 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -269,6 +269,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ຈົນກວ່າຕາເວັນຂຶ້ນ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ເປີດເວລາ <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"ຈົນຮອດ <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"ເປີດໃນເວລານອນ"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"ຈົນກວ່າເວລານອນຈະສິ້ນສຸດ"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string>
@@ -449,8 +451,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ປົດລັອກເພື່ອໃຊ້"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ເກີດບັນຫາໃນການໂຫຼດບັດຂອງທ່ານ, ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"ລະຫັດ QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"ແຕະເພື່ອສະແກນ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດເຮືອບິນ"</string>
@@ -792,9 +793,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້"</string>
@@ -881,8 +888,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ແອັບທີ່ກຳລັງເອີ້ນໃຊ້ໃນພື້ນຫຼັງ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ຢຸດ"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"ສຳເນົາ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"ສຳເນົາແລ້ວ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index f2918d5..8f966fd 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Iki saulėtekio"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Iki <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"ALR"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"ALR išjungtas"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"ALR įjungtas"</string>
@@ -455,8 +459,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Atrakinti, kad būtų galima naudoti"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Gaunant korteles kilo problema, bandykite dar kartą vėliau"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Užrakinimo ekrano nustatymai"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kodas"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Palieskite, kad nuskaitytumėte"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string>
@@ -804,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Valdiklis nepasiekiamas"</string>
@@ -893,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Fone veikiančios programos"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Sustabdyti"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopijuoti"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Nukopijuota"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 86a5df8..402113f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Līdz saullēktam"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Plkst. <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Līdz plkst. <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ir atspējoti"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ir iespējoti"</string>
@@ -452,10 +456,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lai izmantotu, atbloķējiet ekrānu"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ienesot jūsu kartes, radās problēma. Lūdzu, vēlāk mēģiniet vēlreiz."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Bloķēšanas ekrāna iestatījumi"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Ātrās atbildes kods"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Pieskarieties, lai skenētu"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string>
@@ -799,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Notiek atskaņošana ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Vadīkla nav pieejama"</string>
@@ -888,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Lietotnes, kas darbojas fonā"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Apturēt"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopēt"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Nokopēts"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index b21f183..296c3f7 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изгрејсонце"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Се вклучува во <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC е оневозможено"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC е овозможено"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Се репродуцира на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е достапна"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликации се извршуваат во заднина"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Крај"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Копирај"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Копирано"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 798ece0..9be8f92 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"സൂര്യോദയം വരെ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-ന്"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> വരെ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC പ്രവർത്തനക്ഷമമാക്കി"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"നിയന്ത്രണം ലഭ്യമല്ല"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ആപ്പുകൾ പശ്ചാത്തലത്തിൽ റൺ ചെയ്യുന്നു"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"നിർത്തുക"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"പകർത്തുക"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"പകർത്തി"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index d283916..c701e3b 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Нар мандах хүртэл"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-д"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> хүртэл"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-г цуцалсан"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-г идэвхжүүлсэн"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Хяналт боломжгүй байна"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ард ажиллаж байгаа аппууд"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зогсоох"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Хуулах"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Хууллаа"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 933a0e4..33b3817 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सूर्योदयापर्यंत"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> वाजता सुरू होते"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> पर्यंत"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC अक्षम केले आहे"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC सक्षम केले आहे"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले केला जात आहे"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"नियंत्रण उपलब्ध नाही"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ॲप्स बॅकग्राउंडमध्ये रन होत आहेत"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"थांबवा"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"कॉपी करा"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"कॉपी केले"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 4cf476b..75b3361 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hingga matahari trbt"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Dihidupkan pada <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hingga <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dilumpuhkan"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC didayakan"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Terdapat masalah sewaktu mendapatkan kad anda. Sila cuba sebentar lagi"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Tetapan skrin kunci"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Kod QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Ketik untuk membuat imbasan"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kawalan tidak tersedia"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apl berjalan di latar"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Berhenti"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Salin"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Disalin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 8964ed5..e7217c0 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"နေထွက်ချိန် အထိ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> တွင် ဖွင့်မည်"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> အထိ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ကို ပိတ်ထားသည်"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ကို ဖွင့်ထားသည်"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"သုံးရန် လော့ခ်ဖွင့်ပါ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"သင်၏ကတ်များ ရယူရာတွင် ပြဿနာရှိနေသည်၊ နောက်မှ ထပ်စမ်းကြည့်ပါ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR ကုဒ်"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"စကင်ဖတ်ရန် တို့နိုင်သည်"</string>
<string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ထားသည်"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ထိန်းချုပ်မှု မရနိုင်ပါ"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"နောက်ခံတွင် ဖွင့်ထားသောအက်ပ်များ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ရပ်ရန်"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"မိတ္တူကူးရန်"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"ကူးပြီးပါပြီ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 6e66156..f7299de 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Til soloppgang"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Slås på klokken <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Til <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er slått av"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er slått på"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås opp for å bruke"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Det oppsto et problem med henting av kortene. Prøv igjen senere"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Innstillinger for låseskjermen"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kode"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Trykk for å skanne"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrollen er utilgjengelig"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apper som kjører i bakgrunnen"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stopp"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiér"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopiert"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index db64a7e..adacc98 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सूर्योदयसम्म"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> मा सक्रिय"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> सम्म"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC लाई असक्षम पारिएको छ"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC लाई सक्षम पारिएको छ"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"अहिले ब्याकग्राउन्डमा चलिरहेका एप"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"रोक्नुहोस्"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"कपी गर्नुहोस्"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"कपी गरियो"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index b895d9e..fbad27a 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Tot zonsopgang"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aan om <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Tot <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC staat uit"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC staat aan"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontgrendelen om te gebruiken"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Er is een probleem opgetreden bij het ophalen van je kaarten. Probeer het later opnieuw."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Instellingen voor vergrendelscherm"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-code"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Tik om te scannen"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om af te spelen"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Beheeroptie niet beschikbaar"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps die op de achtergrond worden uitgevoerd"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppen"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiëren"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Gekopieerd"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index e47513c..406d90a 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ସକାଳ ପର୍ଯ୍ୟନ୍ତ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ରେ ଚାଲୁ ହେବ"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ପର୍ଯ୍ୟନ୍ତ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ସକ୍ଷମ କରାଯାଇଛି"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ଆପଣଙ୍କ କାର୍ଡଗୁଡ଼ିକ ପାଇବାରେ ଏକ ସମସ୍ୟା ହୋଇଥିଲା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟିଂସ୍"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR କୋଡ"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"ସ୍କାନ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚଲାଇବା ପାଇଁ ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚାଲୁଛି"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ପୃଷ୍ଠପଟରେ ଚାଲୁଥିବା ଆପଗୁଡ଼ିକ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ବନ୍ଦ କରନ୍ତୁ"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"କପି କରନ୍ତୁ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"କପି କରାଯାଇଛି"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 3eae70a..0d84f52 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ਸੂਰਜ ਚੜ੍ਹਨ ਤੱਕ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ਵਜੇ ਚਾਲੂ"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ਵਜੇ ਤੱਕ"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ਨੂੰ ਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ਤੁਹਾਡੇ ਕਾਰਡ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR ਕੋਡ"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"ਸਕੈਨ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲ ਰਹੀਆਂ ਐਪਾਂ"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ਬੰਦ ਕਰੋ"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"ਕਾਪੀ ਕਰੋ"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"ਕਾਪੀ ਕੀਤੀ ਗਈ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 41ad75a..2ab75d0 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do wschodu słońca"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Włącz o <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"Komunikacja NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Komunikacja NFC jest wyłączona"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Komunikacja NFC jest włączona"</string>
@@ -455,10 +459,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odblokuj, aby użyć"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Podczas pobierania kart wystąpił problem. Spróbuj ponownie później."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ustawienia ekranu blokady"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Kod QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Kliknij, aby zeskanować"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -805,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Odtwarzam na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Element jest niedostępny"</string>
@@ -894,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacje działające w tle"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Zatrzymaj"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiuj"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Skopiowano"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index d21ea54..16b5a09 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até o nascer do sol"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativar: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 556e92a..672379b 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até ao amanhecer"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativado à(s) <xliff:g id="TIME">%s</xliff:g>."</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até à(s) <xliff:g id="TIME">%s</xliff:g>."</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"O NFC está desativado"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"O NFC está ativado"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controlo está indisponível"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index d21ea54..16b5a09 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até o nascer do sol"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativar: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Código QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Toque para fazer a leitura"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Reproduzido em <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Apps em execução em segundo plano"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Parar"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiar"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Copiado"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index ab3c186..4d13c03 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Până la răsărit"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Activată la <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Până la <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Serviciul NFC este dezactivat"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Serviciul NFC este activat"</string>
@@ -452,8 +456,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblocați pentru a folosi"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încercați din nou mai târziu"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Cod QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Atingeți pentru a scana"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string>
@@ -798,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anulați"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropiați-vă pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verificați aplicația"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string>
@@ -887,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplicațiile rulează în fundal"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Opriți"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Copiați"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"S-a copiat"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index e92364c..a3e7294 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До рассвета"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Включить в <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"Модуль NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Модуль NFC отключен"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Модуль NFC включен"</string>
@@ -803,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Чтобы начать трансляцию на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Управление недоступно"</string>
@@ -892,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Приложения, работающие в фоновом режиме"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Остановить"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Копировать"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Скопировано."</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 327025c..2cd4865 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"හිරු නගින තෙක්"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ට ක්රියාත්මකයි"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> තෙක්"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC අබලයි"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC සබලයි"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"භාවිත කිරීමට අගුලු හරින්න"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ඔබගේ කාඩ්පත ලබා ගැනීමේ ගැටලුවක් විය, කරුණාකර පසුව නැවත උත්සාහ කරන්න"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"අගුලු තිර සැකසීම්"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR කේතය"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"ස්කෑන් කිරීමට තට්ටු කරන්න"</string>
<string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්රකාරය"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"පාලනය ලබා ගත නොහැකිය"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"පසුබිමින් ධාවනය වෙමින් පවතින යෙදුම්"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"නවත්වන්න"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"පිටපත් කරන්න"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"පිටපත් කරන ලදි"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index bb8870f..8795548 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do východu slnka"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Zapne sa o <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je deaktivované"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je aktivované"</string>
@@ -456,8 +460,7 @@
<string name="wallet_error_generic" msgid="257704570182963611">"Pri načítavaní kariet sa vyskytol problém. Skúste to neskôr."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavenia uzamknutej obrazovky"</string>
<string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kód"</string>
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Ak chcete skenovať, klepnite"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string>
@@ -804,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládač nie je k dispozícii"</string>
@@ -893,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikácie spustené na pozadí"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ukončiť"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopírovať"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Skopírované"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 9c2c32c..2efc74c 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do sončnega vzhoda"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Vklop ob <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Tehnologija NFC je onemogočena"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Tehnologija NFC je omogočena"</string>
@@ -455,8 +459,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odklenite za uporabo"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pri pridobivanju kartic je prišlo do težave. Poskusite znova pozneje."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavitve zaklepanja zaslona"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Koda QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Dotaknite se za optično branje"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string>
@@ -804,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon."</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolnik ni na voljo"</string>
@@ -893,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacije z izvajanjem v ozadju"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ustavi"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiraj"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopirano"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 993ab0ce..70b4dec 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Deri në lindje të diellit"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktiv në <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Deri në <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC është çaktivizuar"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC është aktivizuar"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Shkyçe për ta përdorur"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pati një problem me marrjen e kartave të tua. Provo përsëri më vonë"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cilësimet e ekranit të kyçjes"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Kodi QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Trokit për të skanuar"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolli është i padisponueshëm"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Aplikacionet që ekzekutohen në sfond"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ndalo"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopjo"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"U kopjua"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index f637f2c..219fb70 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -271,6 +271,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изласка сунца"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Укључује се у <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC је онемогућен"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC је омогућен"</string>
@@ -797,9 +801,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Пушта се на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контрола није доступна"</string>
@@ -886,8 +896,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Апликације покренуте у позадини"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Заустави"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Копирај"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Копирано је"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 04cdcbe..a074e1f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Till soluppgången"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktivera kl. <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Till <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC är inaktiverat"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC är aktiverat"</string>
@@ -450,8 +454,7 @@
<string name="wallet_error_generic" msgid="257704570182963611">"Det gick inte att hämta dina kort. Försök igen senare."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Inställningar för låsskärm"</string>
<string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-kod"</string>
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Tryck för att skanna"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Styrning är inte tillgänglig"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Appar som körs i bakgrunden"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stoppa"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopiera"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopierades"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 26184849..f243c19 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hadi macheo"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Itawashwa saa <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hadi saa <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC imezimwa"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC imewashwa"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Fungua ili utumie"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Hitilafu imetokea wakati wa kuleta kadi zako, tafadhali jaribu tena baadaye"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mipangilio ya kufunga skrini"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Msimbo wa QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Gusa ili uchanganue"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sogea karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kidhibiti hakipatikani"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Programu zinazotumika chinichini"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Simamisha"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Nakili"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Imenakiliwa"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index e45f072..0b57005 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"காலை வரை"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>க்கு ஆன் செய்"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> வரை"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC முடக்கப்பட்டது"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC இயக்கப்பட்டது"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் இயக்க உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லுங்கள்"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"கட்டுப்பாடு இல்லை"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"பின்னணியில் இயங்கும் ஆப்ஸ்"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"நிறுத்து"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"நகலெடு"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"நகலெடுக்கப்பட்டது"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4e41023..d479c0d 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"సూర్యోదయం వరకు"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> కు ఆన్ అవుతుంది"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> వరకు"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC నిలిపివేయబడింది"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ప్రారంభించబడింది"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ఇన్యాక్టివ్, యాప్ చెక్ చేయండి"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"కంట్రోల్ అందుబాటులో లేదు"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్ను ఎంచుకోండి"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"యాప్లు బ్యాక్గ్రౌండ్లో రన్ అవుతున్నాయి"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"ఆపివేయండి"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"కాపీ చేయండి"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"కాపీ అయింది"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 8897186..03d2566 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"จนพระอาทิตย์ขึ้น"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"เปิดเวลา <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"จนถึง <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ถูกปิดใช้งาน"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"เปิดใช้งาน NFC แล้ว"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ขยับเข้ามาใกล้ขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ใช้การควบคุมไม่ได้"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"แอปที่ทำงานอยู่เบื้องหลัง"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"หยุด"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"คัดลอก"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"คัดลอกแล้ว"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 6ab6ef6..c33d211 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hanggang sunrise"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ma-o-on nang <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hanggang <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"Naka-disable ang NFC"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"Naka-enable ang NFC"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"I-unlock para magamit"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Nagkaproblema sa pagkuha ng iyong mga card, pakisubukan ulit sa ibang pagkakataon"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mga setting ng lock screen"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR code"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"I-tap para i-scan"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Hindi available ang kontrol"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Mga app na tumatakbo sa background"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Ihinto"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopyahin"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Nakopya"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ee44b76..ecef9ee 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Sabaha kadar"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Açılacağı saat: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Şu saate kadar: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC devre dışı"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC etkin"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Kullanmak için kilidi aç"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kartlarınız alınırken bir sorun oluştu. Lütfen daha sonra tekrar deneyin"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilit ekranı ayarları"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kodu"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Taramak için dokunun"</string>
<string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalmak için yaklaşın"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalınıyor"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol kullanılamıyor"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Arka planda çalışan uygulamalar"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Durdur"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopyala"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Kopyalandı"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 27aad2e..4aba37b 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -273,6 +273,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До сходу сонця"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Вмикається о <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC вимкнено"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ввімкнено"</string>
@@ -455,10 +459,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Розблокувати, щоб використовувати"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Не вдалось отримати ваші картки. Повторіть спробу пізніше."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Параметри блокування екрана"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR-код"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Натисніть, щоб сканувати"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string>
@@ -805,9 +807,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Щоб відтворити контент на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Елемент керування недоступний"</string>
@@ -894,8 +902,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Додатки, що працюють у фоновому режимі"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Зупинити"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Копіювати"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Скопійовано"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 015d520..31c0c48 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"طلوع آفتاب تک"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"آن ہوگی بوقت <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> تک"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC غیر فعال ہے"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC فعال ہے"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"کنٹرول دستیاب نہیں ہے"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"ایپس پس منظر میں چل رہی ہیں"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"روکیں"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"کاپی کریں"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"کاپی کر دیا گیا ہے"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index ce7fc32..80f5d21 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Quyosh chiqqunicha"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> da yoqiladi"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> gacha"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC o‘chiq"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC yoniq"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Foydalanish uchun qulfdan chiqarish"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Bildirgilarni yuklashda xatolik yuz berdi, keyinroq qaytadan urining"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Qulflangan ekran sozlamalari"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"QR kod"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Skanerlash uchun bosing"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Qaytarish"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>da ijro etish uchun yaqinroq keling"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilinmoqda"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Boshqarish imkonsiz"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Orqa fonda ishlayotgan ilovalar"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Stop"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Nusxa olish"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Nusxa olindi"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index e4066d9e..c323236 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Cho đến khi trời sáng"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Bật vào lúc <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Cho đến <xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC đã được tắt"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC đã được bật"</string>
@@ -449,10 +453,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Mở khóa để sử dụng"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Đã xảy ra sự cố khi tải thẻ của bạn. Vui lòng thử lại sau"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cài đặt màn hình khóa"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Mã QR"</string>
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"Nhấn để quét"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ máy bay"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy báo thức tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string>
@@ -793,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Không có chức năng điều khiển"</string>
@@ -882,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Các ứng dụng chạy trong nền"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Dừng"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Sao chép"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Đã sao chép"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 4666a88..b236060 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"在日出时关闭"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"在<xliff:g id="TIME">%s</xliff:g> 开启"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"直到<xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已启用"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"若要在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放,请靠近这台设备"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"控件不可用"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在在后台运行的应用"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"复制"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"已复制"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f64e78c..4155ccf 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"在日出時關閉"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"於<xliff:g id="TIME">%s</xliff:g>開啟"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"直至<xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string>
@@ -791,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"正在 <xliff:g id="DEVICENAME">%1$s</xliff:g> 上播放"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制功能"</string>
@@ -880,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"正在背景中執行的應用程式"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"複製"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"已複製"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 87a6ae8..bf5974f 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"於日出時關閉"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"開啟時間:<xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"關閉時間:<xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string>
@@ -450,8 +454,7 @@
<string name="wallet_error_generic" msgid="257704570182963611">"擷取卡片時發生問題,請稍後再試"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"螢幕鎖定設定"</string>
<string name="qr_code_scanner_title" msgid="5660820608548306581">"QR 圖碼"</string>
- <!-- no translation found for qr_code_scanner_description (7937603775306661863) -->
- <skip />
+ <string name="qr_code_scanner_description" msgid="7937603775306661863">"輕觸即可掃描"</string>
<string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制項"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"目前在背景執行的應用程式"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"停止"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"複製"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"已複製"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 602845b..3d8e8a4 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -269,6 +269,10 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Kuze kube sekuphumeni kwelanga"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Kuvulwe ngo-<xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Kuze kube ngu-<xliff:g id="TIME">%s</xliff:g>"</string>
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_on_at_bedtime (2274300599408864897) -->
+ <skip />
+ <!-- no translation found for quick_settings_dark_mode_secondary_label_until_bedtime_ends (1790772410777123685) -->
+ <skip />
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"I-NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"I-NFC ikhutshaziwe"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"I-NFC inikwe amandla"</string>
@@ -449,8 +453,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Vula ukuze usebenzise"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kube khona inkinga yokuthola amakhadi akho, sicela uzame futhi ngemuva kwesikhathi"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Amasethingi okukhiya isikrini"</string>
- <!-- no translation found for qr_code_scanner_title (5660820608548306581) -->
- <skip />
+ <string name="qr_code_scanner_title" msgid="5660820608548306581">"Ikhodi ye-QR"</string>
<string name="qr_code_scanner_description" msgid="7937603775306661863">"Thepha ukuze uskene"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string>
@@ -792,9 +795,15 @@
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string>
- <!-- no translation found for media_move_closer_to_start_cast (2673104707465013176) -->
+ <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_move_closer_to_end_cast (6495907340926563656) -->
<skip />
- <string name="media_transfer_playing" msgid="3760048096352107789">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+ <!-- no translation found for media_transfer_playing_different_device (7186806382609785610) -->
+ <skip />
+ <!-- no translation found for media_transfer_playing_this_device (1856890686844499172) -->
+ <skip />
+ <!-- no translation found for media_transfer_failed (2640354446629980227) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ukulawula akutholakali"</string>
@@ -881,8 +890,6 @@
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string>
<string name="fgs_manager_dialog_title" msgid="656091833603337197">"Ama-app ayaqhubeka ngemuva"</string>
<string name="fgs_manager_app_item_stop_button_label" msgid="7188317969020801156">"Misa"</string>
- <!-- no translation found for clipboard_edit_text_copy (770856373439969178) -->
- <skip />
- <!-- no translation found for clipboard_overlay_text_copied (1872624400464891363) -->
- <skip />
+ <string name="clipboard_edit_text_copy" msgid="770856373439969178">"Kopisha"</string>
+ <string name="clipboard_overlay_text_copied" msgid="1872624400464891363">"Ikopishiwe"</string>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9d7cf1a..079f5d0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -309,6 +309,7 @@
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.biometrics.AuthController</item>
+ <item>com.android.systemui.log.SessionTracker</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.theme.ThemeOverlayController</item>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index af7ef53..f94031c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -432,7 +432,9 @@
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">95dp</dimen>
- <dimen name="rounded_corner_content_padding">0dp</dimen>
+ <dimen name="rounded_corner_content_padding">
+ @*android:dimen/rounded_corner_content_padding
+ </dimen>
<dimen name="navigation_key_padding">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6eab2b2..75ae52c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2360,12 +2360,12 @@
<!-- Label for the entry point to open the dialog which shows currently running applications [CHAR LIMIT=NONE]-->
<plurals name="fgs_manager_footer_label">
- <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> app running in the background</item>
- <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> apps running in the background</item>
+ <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item>
+ <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item>
</plurals>
- <!-- Title for dialog listing applications currently running in the backing [CHAR LIMIT=NONE]-->
- <string name="fgs_manager_dialog_title">Apps running in the background</string>
- <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]-->
+ <!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]-->
+ <string name="fgs_manager_dialog_title">Active apps</string>
+ <!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
<string name="fgs_manager_app_item_stop_button_label">Stop</string>
<!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index f86d08d..7f456db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -25,6 +25,7 @@
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.SystemClock;
+import android.util.PluralsMessageFormatter;
import android.view.KeyEvent;
import com.android.internal.util.LatencyTracker;
@@ -38,6 +39,9 @@
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
+import java.util.HashMap;
+import java.util.Map;
+
public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKeyInputView>
extends KeyguardInputViewController<T> {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -152,9 +156,12 @@
@Override
public void onTick(long millisUntilFinished) {
int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mMessageAreaController.setMessage(mView.getResources().getQuantityString(
- R.plurals.kg_too_many_failed_attempts_countdown,
- secondsRemaining, secondsRemaining));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", secondsRemaining);
+ mMessageAreaController.setMessage(PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown));
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 238acd5..0b4bc9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -23,6 +23,7 @@
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.SystemClock;
+import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.View;
@@ -40,7 +41,9 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class KeyguardPatternViewController
extends KeyguardInputViewController<KeyguardPatternView> {
@@ -366,9 +369,13 @@
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
- mMessageAreaController.setMessage(mView.getResources().getQuantityString(
- R.plurals.kg_too_many_failed_attempts_countdown,
- secondsRemaining, secondsRemaining));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", secondsRemaining);
+
+ mMessageAreaController.setMessage(PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown));
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 3fab724..c387260 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -20,6 +20,8 @@
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
import static java.lang.Integer.max;
@@ -892,6 +894,9 @@
} else {
textView.setBackground(null);
}
+ view.setEnabled(item.isSwitchToEnabled);
+ view.setAlpha(view.isEnabled() ? USER_SWITCH_ENABLED_ALPHA :
+ USER_SWITCH_DISABLED_ALPHA);
return view;
}
@@ -941,6 +946,7 @@
mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView parent, View view, int pos, long id) {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
+ if (!view.isEnabled()) return;
// Subtract one for the header
UserRecord user = adapter.getItem(pos - 1);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 9c2971c..783415e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -32,6 +32,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Dimension;
+import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -87,6 +88,11 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.decor.DecorProvider;
+import com.android.systemui.decor.DecorProviderFactory;
+import com.android.systemui.decor.DecorProviderKt;
+import com.android.systemui.decor.OverlayWindow;
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -104,6 +110,8 @@
import javax.inject.Inject;
+import kotlin.Pair;
+
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
* for antialiasing and emulation purposes.
@@ -135,6 +143,7 @@
private final UserTracker mUserTracker;
private final PrivacyDotViewController mDotViewController;
private final ThreadFactory mThreadFactory;
+ private final DecorProviderFactory mDotFactory;
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -146,14 +155,9 @@
@VisibleForTesting
protected Point mRoundedDefaultBottom = new Point(0, 0);
@VisibleForTesting
- protected View[] mOverlays;
+ protected OverlayWindow[] mOverlays = null;
@Nullable
private DisplayCutoutView[] mCutoutViews;
- //TODO:
- View mTopLeftDot;
- View mTopRightDot;
- View mBottomLeftDot;
- View mBottomRightDot;
private float mDensity;
private WindowManager mWindowManager;
private int mRotation;
@@ -162,7 +166,6 @@
private Handler mHandler;
private boolean mPendingRotationChange;
private boolean mIsRoundedCornerMultipleRadius;
- private boolean mIsPrivacyDotEnabled;
private Drawable mRoundedCornerDrawable;
private Drawable mRoundedCornerDrawableTop;
private Drawable mRoundedCornerDrawableBottom;
@@ -227,7 +230,8 @@
TunerService tunerService,
UserTracker userTracker,
PrivacyDotViewController dotViewController,
- ThreadFactory threadFactory) {
+ ThreadFactory threadFactory,
+ PrivacyDotDecorProviderFactory dotFactory) {
super(context);
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
@@ -236,6 +240,7 @@
mUserTracker = userTracker;
mDotViewController = dotViewController;
mThreadFactory = threadFactory;
+ mDotFactory = dotFactory;
}
@Override
@@ -250,11 +255,14 @@
mDotViewController.setUiExecutor(mExecutor);
}
+ private boolean isPrivacyDotEnabled() {
+ return mDotFactory.getHasProviders();
+ }
+
private void startOnScreenDecorationsThread() {
mRotation = mContext.getDisplay().getRotation();
mDisplayUniqueId = mContext.getDisplay().getUniqueId();
mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
- mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
updateRoundedCornerDrawable();
@@ -292,8 +300,9 @@
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] != null) {
- mOverlays[i].getViewTreeObserver().addOnPreDrawListener(
- new RestartingPreDrawListener(mOverlays[i], i, newRotation));
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ overlayView.getViewTreeObserver().addOnPreDrawListener(
+ new RestartingPreDrawListener(overlayView, i, newRotation));
}
}
}
@@ -313,24 +322,64 @@
updateOrientation();
}
+ @Nullable
+ private View getOverlayView(@IdRes int id) {
+ if (mOverlays == null) {
+ return null;
+ }
+
+ for (final OverlayWindow overlay : mOverlays) {
+ if (overlay == null) {
+ continue;
+ }
+
+ final View view = overlay.getView(id);
+ if (view != null) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ private void removeOverlayView(@IdRes int id) {
+ if (mOverlays == null) {
+ return;
+ }
+
+ for (final OverlayWindow overlay : mOverlays) {
+ if (overlay == null) {
+ continue;
+ }
+
+ overlay.removeView(id);
+ }
+ }
+
private void setupDecorations() {
- if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) {
+ List<DecorProvider> decorProviders = mDotFactory.getProviders();
+
+ if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) {
final DisplayCutout cutout = getCutout();
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
|| shouldShowPrivacyDot(i, cutout)) {
- createOverlay(i, cutout);
+ Pair<List<DecorProvider>, List<DecorProvider>> pair =
+ DecorProviderKt.partitionAlignedBound(decorProviders, i);
+ decorProviders = pair.getSecond();
+ createOverlay(i, cutout, pair.getFirst());
} else {
removeOverlay(i);
}
}
- if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null
- && mBottomRightDot != null) {
+ final View tl, tr, bl, br;
+ if ((tl = getOverlayView(R.id.privacy_dot_top_left_container)) != null
+ && (tr = getOverlayView(R.id.privacy_dot_top_right_container)) != null
+ && (bl = getOverlayView(R.id.privacy_dot_bottom_left_container)) != null
+ && (br = getOverlayView(R.id.privacy_dot_bottom_right_container)) != null) {
// Overlays have been created, send the dots to the controller
//TODO: need a better way to do this
- mDotViewController.initialize(
- mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
+ mDotViewController.initialize(tl, tr, bl, br);
}
} else {
removeAllOverlays();
@@ -414,13 +463,16 @@
if (mOverlays == null || mOverlays[pos] == null) {
return;
}
- mWindowManager.removeViewImmediate(mOverlays[pos]);
+ mWindowManager.removeViewImmediate(mOverlays[pos].getRootView());
mOverlays[pos] = null;
}
- private void createOverlay(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ private void createOverlay(
+ @BoundsPosition int pos,
+ @Nullable DisplayCutout cutout,
+ @NonNull List<DecorProvider> decorProviders) {
if (mOverlays == null) {
- mOverlays = new View[BOUNDS_POSITION_LENGTH];
+ mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
}
if (mCutoutViews == null) {
@@ -430,78 +482,49 @@
if (mOverlays[pos] != null) {
return;
}
- mOverlays[pos] = overlayForPosition(pos, cutout);
+
+ mOverlays[pos] = overlayForPosition(pos, decorProviders);
mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
- ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
+ mOverlays[pos].getRootView().addView(mCutoutViews[pos]);
- mOverlays[pos].setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mOverlays[pos].setAlpha(0);
- mOverlays[pos].setForceDarkAllowed(false);
+ final ViewGroup overlayView = mOverlays[pos].getRootView();
+ overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ overlayView.setAlpha(0);
+ overlayView.setForceDarkAllowed(false);
updateView(pos, cutout);
- mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos));
+ mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
- mOverlays[pos].addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mOverlays[pos].removeOnLayoutChangeListener(this);
- mOverlays[pos].animate()
+ overlayView.removeOnLayoutChangeListener(this);
+ overlayView.animate()
.alpha(1)
.setDuration(1000)
.start();
}
});
- mOverlays[pos].getViewTreeObserver().addOnPreDrawListener(
- new ValidatingPreDrawListener(mOverlays[pos]));
+ mOverlays[pos].getRootView().getViewTreeObserver().addOnPreDrawListener(
+ new ValidatingPreDrawListener(mOverlays[pos].getRootView()));
}
/**
* Allow overrides for top/bottom positions
*/
- private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
- final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP)
- ? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom;
- final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null);
- initPrivacyDotView(vg, pos, cutout);
- return vg;
- }
-
- private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos,
- @Nullable DisplayCutout cutout) {
- final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
- final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
- if (!shouldShowPrivacyDot(pos, cutout)) {
- viewGroup.removeView(left);
- viewGroup.removeView(right);
- return;
- }
-
- switch (pos) {
- case BOUNDS_POSITION_LEFT: {
- mTopLeftDot = left;
- mBottomLeftDot = right;
- break;
- }
- case BOUNDS_POSITION_TOP: {
- mTopLeftDot = left;
- mTopRightDot = right;
- break;
- }
- case BOUNDS_POSITION_RIGHT: {
- mTopRightDot = left;
- mBottomRightDot = right;
- break;
- }
- case BOUNDS_POSITION_BOTTOM: {
- mBottomLeftDot = left;
- mBottomRightDot = right;
- break;
- }
- }
+ private OverlayWindow overlayForPosition(
+ @BoundsPosition int pos,
+ @NonNull List<DecorProvider> decorProviders) {
+ final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos);
+ decorProviders.forEach(provider -> {
+ removeOverlayView(provider.getViewId());
+ currentOverlay.addDecorProvider(provider, mRotation);
+ });
+ return currentOverlay;
}
private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
@@ -638,12 +661,15 @@
if (mOverlays[i] == null) {
continue;
}
- final int size = ((ViewGroup) mOverlays[i]).getChildCount();
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ final int size = overlayView.getChildCount();
View child;
for (int j = 0; j < size; j++) {
- child = ((ViewGroup) mOverlays[i]).getChildAt(j);
- if (child.getId() == R.id.privacy_dot_left_container
- || child.getId() == R.id.privacy_dot_right_container) {
+ child = overlayView.getChildAt(j);
+ if (child.getId() == R.id.privacy_dot_top_left_container
+ || child.getId() == R.id.privacy_dot_top_right_container
+ || child.getId() == R.id.privacy_dot_bottom_left_container
+ || child.getId() == R.id.privacy_dot_bottom_right_container) {
// Exclude privacy dot from color inversion (for now?)
continue;
}
@@ -684,13 +710,18 @@
pw.println("ScreenDecorations state:");
pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
- pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled);
+ pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
pw.println(" mPendingRotationChange:" + mPendingRotationChange);
pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
+ ")");
pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + ","
+ mRoundedDefaultBottom.y + ")");
+ pw.println(" mOverlays(left,top,right,bottom)=("
+ + (mOverlays != null && mOverlays[BOUNDS_POSITION_LEFT] != null) + ","
+ + (mOverlays != null && mOverlays[BOUNDS_POSITION_TOP] != null) + ","
+ + (mOverlays != null && mOverlays[BOUNDS_POSITION_RIGHT] != null) + ","
+ + (mOverlays != null && mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")");
}
private void updateOrientation() {
@@ -843,7 +874,7 @@
private void updateRoundedCornerView(@BoundsPosition int pos, int id,
@Nullable DisplayCutout cutout) {
- final View rounded = mOverlays[pos].findViewById(id);
+ final View rounded = mOverlays[pos].getRootView().findViewById(id);
if (rounded == null) {
return;
}
@@ -929,7 +960,7 @@
}
private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
- return mIsPrivacyDotEnabled && isDefaultShownOverlayPos(pos, cutout);
+ return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout);
}
private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
@@ -955,7 +986,7 @@
if (mOverlays[i] == null) {
continue;
}
- mWindowManager.updateViewLayout(mOverlays[i], getWindowLayoutParams(i));
+ mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i));
}
}
@@ -1003,9 +1034,10 @@
if (mOverlays[i] == null) {
continue;
}
- ((ImageView) mOverlays[i].findViewById(R.id.left)).setImageDrawable(
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable(
isTopRoundedCorner(i, R.id.left) ? top : bottom);
- ((ImageView) mOverlays[i].findViewById(R.id.right)).setImageDrawable(
+ ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable(
isTopRoundedCorner(i, R.id.right) ? top : bottom);
}
}
@@ -1047,9 +1079,10 @@
if (mOverlays[i] == null) {
continue;
}
- setSize(mOverlays[i].findViewById(R.id.left),
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ setSize(overlayView.findViewById(R.id.left),
isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom);
- setSize(mOverlays[i].findViewById(R.id.right),
+ setSize(overlayView.findViewById(R.id.right),
isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index f833c2a..b0f7e55 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -170,6 +170,10 @@
mCurrentDialog = null;
mOrientationListener.disable();
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
try {
if (mReceiver != null) {
mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -200,6 +204,10 @@
mCurrentDialog = null;
mOrientationListener.disable();
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
if (mReceiver != null) {
mReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -462,6 +470,7 @@
Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
return;
}
+
try {
mReceiver.onDialogDismissed(reason, credentialAttestation);
} catch (RemoteException e) {
@@ -816,6 +825,9 @@
}
mReceiver = (IBiometricSysuiReceiver) args.arg2;
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptShown();
+ }
mCurrentDialog = newDialog;
mCurrentDialog.show(mWindowManager, savedState);
mOrientationListener.enable();
@@ -826,6 +838,11 @@
if (mCurrentDialog == null) {
Log.w(TAG, "Dialog already dismissed");
}
+
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
mReceiver = null;
mCurrentDialog = null;
mOrientationListener.disable();
@@ -897,12 +914,22 @@
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
*/
- void onAllAuthenticatorsRegistered();
+ default void onAllAuthenticatorsRegistered() {}
/**
* Called when UDFPS enrollments have changed. This is called after boot and on changes to
* enrollment.
*/
- void onEnrollmentsChanged();
+ default void onEnrollmentsChanged() {}
+
+ /**
+ * Called when the biometric prompt starts showing.
+ */
+ default void onBiometricPromptShown() {}
+
+ /**
+ * Called when the biometric prompt is no longer showing.
+ */
+ default void onBiometricPromptDismissed() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 0fb1295..57ca0f4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -16,6 +16,12 @@
package com.android.systemui.biometrics;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -374,7 +380,7 @@
final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
.setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
.setMessage(
- getLastAttemptBeforeWipeMessageRes(getUserTypeForWipe(), mCredentialType))
+ getLastAttemptBeforeWipeMessage(getUserTypeForWipe(), mCredentialType))
.setPositiveButton(android.R.string.ok, null)
.create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
@@ -383,7 +389,7 @@
private void showNowWipingDialog() {
final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setMessage(getNowWipingMessageRes(getUserTypeForWipe()))
+ .setMessage(getNowWipingMessage(getUserTypeForWipe()))
.setPositiveButton(R.string.biometric_dialog_now_wiping_dialog_dismiss, null)
.setOnDismissListener(
dialog -> mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR))
@@ -404,70 +410,121 @@
}
}
- private static @StringRes int getLastAttemptBeforeWipeMessageRes(
+ private String getLastAttemptBeforeWipeMessage(
@UserType int userType, @Utils.CredentialType int credentialType) {
switch (userType) {
case USER_TYPE_PRIMARY:
- return getLastAttemptBeforeWipeDeviceMessageRes(credentialType);
+ return getLastAttemptBeforeWipeDeviceMessage(credentialType);
case USER_TYPE_MANAGED_PROFILE:
- return getLastAttemptBeforeWipeProfileMessageRes(credentialType);
+ return getLastAttemptBeforeWipeProfileMessage(credentialType);
case USER_TYPE_SECONDARY:
- return getLastAttemptBeforeWipeUserMessageRes(credentialType);
+ return getLastAttemptBeforeWipeUserMessage(credentialType);
default:
throw new IllegalArgumentException("Unrecognized user type:" + userType);
}
}
- private static @StringRes int getLastAttemptBeforeWipeDeviceMessageRes(
+ private String getLastAttemptBeforeWipeDeviceMessage(
@Utils.CredentialType int credentialType) {
switch (credentialType) {
case Utils.CREDENTIAL_PIN:
- return R.string.biometric_dialog_last_pin_attempt_before_wipe_device;
+ return mContext.getString(
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_device);
case Utils.CREDENTIAL_PATTERN:
- return R.string.biometric_dialog_last_pattern_attempt_before_wipe_device;
+ return mContext.getString(
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_device);
case Utils.CREDENTIAL_PASSWORD:
default:
- return R.string.biometric_dialog_last_password_attempt_before_wipe_device;
+ return mContext.getString(
+ R.string.biometric_dialog_last_password_attempt_before_wipe_device);
}
}
- private static @StringRes int getLastAttemptBeforeWipeProfileMessageRes(
+ private String getLastAttemptBeforeWipeProfileMessage(
+ @Utils.CredentialType int credentialType) {
+ return mDevicePolicyManager.getString(
+ getLastAttemptBeforeWipeProfileUpdatableStringId(credentialType),
+ () -> getLastAttemptBeforeWipeProfileDefaultMessage(credentialType));
+ }
+
+ private static String getLastAttemptBeforeWipeProfileUpdatableStringId(
@Utils.CredentialType int credentialType) {
switch (credentialType) {
case Utils.CREDENTIAL_PIN:
- return R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
+ return BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
case Utils.CREDENTIAL_PATTERN:
- return R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
+ return BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
case Utils.CREDENTIAL_PASSWORD:
default:
- return R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
+ return BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
}
}
- private static @StringRes int getLastAttemptBeforeWipeUserMessageRes(
+ private String getLastAttemptBeforeWipeProfileDefaultMessage(
@Utils.CredentialType int credentialType) {
+ int resId;
switch (credentialType) {
case Utils.CREDENTIAL_PIN:
- return R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
+ resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
+ break;
case Utils.CREDENTIAL_PATTERN:
- return R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
+ resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
+ break;
case Utils.CREDENTIAL_PASSWORD:
default:
- return R.string.biometric_dialog_last_password_attempt_before_wipe_user;
+ resId = R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
+ }
+ return mContext.getString(resId);
+ }
+
+ private String getLastAttemptBeforeWipeUserMessage(
+ @Utils.CredentialType int credentialType) {
+ int resId;
+ switch (credentialType) {
+ case Utils.CREDENTIAL_PIN:
+ resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
+ break;
+ case Utils.CREDENTIAL_PATTERN:
+ resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
+ break;
+ case Utils.CREDENTIAL_PASSWORD:
+ default:
+ resId = R.string.biometric_dialog_last_password_attempt_before_wipe_user;
+ }
+ return mContext.getString(resId);
+ }
+
+ private String getNowWipingMessage(@UserType int userType) {
+ return mDevicePolicyManager.getString(
+ getNowWipingUpdatableStringId(userType),
+ () -> getNowWipingDefaultMessage(userType));
+ }
+
+ private String getNowWipingUpdatableStringId(@UserType int userType) {
+ switch (userType) {
+ case USER_TYPE_MANAGED_PROFILE:
+ return BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
+ default:
+ return UNDEFINED;
}
}
- private static @StringRes int getNowWipingMessageRes(@UserType int userType) {
+ private String getNowWipingDefaultMessage(@UserType int userType) {
+ int resId;
switch (userType) {
case USER_TYPE_PRIMARY:
- return R.string.biometric_dialog_failed_attempts_now_wiping_device;
+ resId = R.string.biometric_dialog_failed_attempts_now_wiping_device;
+ break;
case USER_TYPE_MANAGED_PROFILE:
- return R.string.biometric_dialog_failed_attempts_now_wiping_profile;
+ resId = R.string.biometric_dialog_failed_attempts_now_wiping_profile;
+ break;
case USER_TYPE_SECONDARY:
- return R.string.biometric_dialog_failed_attempts_now_wiping_user;
+ resId = R.string.biometric_dialog_failed_attempts_now_wiping_user;
+ break;
default:
throw new IllegalArgumentException("Unrecognized user type:" + userType);
}
+ return mContext.getString(resId);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
index b7404df..dfbe348 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
@@ -37,7 +37,7 @@
private val onChanged: () -> Unit
) : DisplayManager.DisplayListener {
- private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
+ private var lastRotation = Surface.ROTATION_0
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
@@ -63,6 +63,7 @@
/** Listen for changes. */
fun enable() {
+ lastRotation = context.display?.rotation ?: Surface.ROTATION_0
displayManager.registerDisplayListener(this, handler)
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 41a4963..0e1cd51 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -60,8 +60,8 @@
return;
}
if (mClipboardOverlayController == null) {
- mClipboardOverlayController = new ClipboardOverlayController(mContext,
- new TimeoutHandler(mContext));
+ mClipboardOverlayController =
+ new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
}
mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
mClipboardOverlayController.setOnSessionCompleteListener(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index caf0307..b6bcb87 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -28,6 +28,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.MainThread;
+import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
@@ -43,6 +44,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
@@ -60,8 +62,13 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.policy.PhoneWindow;
@@ -71,6 +78,7 @@
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
+import java.util.ArrayList;
/**
* Controls state and UI for the overlay that appears when something is added to the clipboard
@@ -93,6 +101,7 @@
private final PhoneWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final AccessibilityManager mAccessibilityManager;
+ private final TextClassifier mTextClassifier;
private final DraggableConstraintLayout mView;
private final ImageView mImagePreview;
@@ -101,9 +110,13 @@
private final ScreenshotActionChip mRemoteCopyChip;
private final View mActionContainerBackground;
private final View mDismissButton;
+ private final LinearLayout mActionContainer;
+ private final ArrayList<ScreenshotActionChip> mActionChips = new ArrayList<>();
private Runnable mOnSessionCompleteListener;
+
+ private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
private BroadcastReceiver mCloseDialogsReceiver;
@@ -117,6 +130,8 @@
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+ .getTextClassifier();
mWindowManager = mContext.getSystemService(WindowManager.class);
@@ -134,8 +149,9 @@
mView = (DraggableConstraintLayout)
LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
- mActionContainerBackground = requireNonNull(
- mView.findViewById(R.id.actions_container_background));
+ mActionContainerBackground =
+ requireNonNull(mView.findViewById(R.id.actions_container_background));
+ mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
@@ -143,7 +159,7 @@
mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
mView.setOnDismissCallback(this::hideImmediate);
- mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
+ mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout);
mDismissButton.setOnClickListener(view -> animateOut());
@@ -166,7 +182,7 @@
withWindowAttached(() -> {
mWindow.setContentView(mView);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
- mView.post(() -> getEnterAnimation().start());
+ mView.post(this::animateIn);
});
mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
@@ -199,12 +215,15 @@
void setClipData(ClipData clipData) {
reset();
-
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ showTextPreview(mContext.getResources().getString(
+ R.string.clipboard_overlay_text_copied));
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
- showEditableText(clipData.getItemAt(0).getText());
+ ClipData.Item item = clipData.getItemAt(0);
+ if (item.getTextLinks() != null) {
+ AsyncTask.execute(() -> classifyText(clipData.getItemAt(0)));
+ }
+ showEditableText(item.getText());
} else if (clipData.getItemAt(0).getUri() != null) {
// How to handle non-image URIs?
showEditableImage(clipData.getItemAt(0).getUri());
@@ -212,7 +231,6 @@
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
}
-
mTimeoutHandler.resetTimeout();
}
@@ -220,10 +238,40 @@
mOnSessionCompleteListener = runnable;
}
+ private void classifyText(ClipData.Item item) {
+ ArrayList<RemoteAction> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+ TextClassification classification = mTextClassifier.classifyText(
+ item.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
+ mView.post(() -> {
+ for (ScreenshotActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ for (RemoteAction action : actions) {
+ ScreenshotActionChip chip = constructActionChip(action);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ }
+ });
+ }
+
+ private ScreenshotActionChip constructActionChip(RemoteAction action) {
+ ScreenshotActionChip chip = (ScreenshotActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.screenshot_action_chip, mActionContainer, false);
+ chip.setText(action.getTitle());
+ chip.setIcon(action.getIcon(), false);
+ chip.setPendingIntent(action.getActionIntent(), this::animateOut);
+ chip.setAlpha(1);
+ return chip;
+ }
+
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
- InputMonitor monitor = inputManager.monitorGestureInput("clipboard overlay", 0);
- mInputEventReceiver = new InputEventReceiver(monitor.getInputChannel(),
+ mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+ mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
@@ -311,6 +359,10 @@
return nearbyIntent;
}
+ private void animateIn() {
+ getEnterAnimation().start();
+ }
+
private void animateOut() {
getExitAnimation().start();
}
@@ -390,6 +442,10 @@
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
if (mOnSessionCompleteListener != null) {
mOnSessionCompleteListener.run();
}
@@ -398,6 +454,10 @@
private void reset() {
mView.setTranslationX(0);
mView.setAlpha(0);
+ for (ScreenshotActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
mTimeoutHandler.cancelTimeout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index bbe9dbd..96e2302 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -29,6 +29,7 @@
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -66,6 +67,12 @@
@ClassKey(AuthController.class)
public abstract CoreStartable bindAuthController(AuthController service);
+ /** Inject into SessionTracker. */
+ @Binds
+ @IntoMap
+ @ClassKey(SessionTracker.class)
+ public abstract CoreStartable bindSessionTracker(SessionTracker service);
+
/** Inject into GarbageMonitor.Service. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
new file mode 100644
index 0000000..3543bb4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+import android.view.DisplayCutout
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+
+/**
+ * An interface for providing view with a specific functionality. Take an example, if privacy dot
+ * is enabled, there are 4 DecorProviders which are used to provide privacy dot views on top-left,
+ * top-right, bottom-left, bottom-right.
+ */
+abstract class DecorProvider {
+
+ /** Id for the view which is created through inflateView() */
+ abstract val viewId: Int
+
+ /** The number of total aligned bounds */
+ val numOfAlignedEdge: Int
+ get() = alignedBounds.size
+
+ /** The aligned bounds for the view which is created through inflateView() */
+ abstract val alignedBounds: List<Int>
+
+ /** Inflate view into parent as current rotation */
+ abstract fun inflateView(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ @Surface.Rotation rotation: Int
+ ): View
+}
+
+/**
+ * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this
+ * alignedBound element. The providers on the second list do not contain this alignedBound element
+ */
+fun List<DecorProvider>.partitionAlignedBound(
+ @DisplayCutout.BoundsPosition alignedBound: Int
+): Pair<List<DecorProvider>, List<DecorProvider>> {
+ return partition { it.alignedBounds.contains(alignedBound) }
+}
+
+/**
+ * A provider for view shown on corner.
+ */
+abstract class CornerDecorProvider : DecorProvider() {
+ /** The first bound which a corner view is aligned based on rotation 0 */
+ @DisplayCutout.BoundsPosition protected abstract val alignedBound1: Int
+ /** The second bound which a corner view is aligned based on rotation 0 */
+ @DisplayCutout.BoundsPosition protected abstract val alignedBound2: Int
+
+ override val alignedBounds: List<Int> by lazy {
+ listOf(alignedBound1, alignedBound2)
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.aidl b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
similarity index 71%
copy from core/java/android/view/selectiontoolbar/SelectionContext.aidl
copy to packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
index 5206831..c60cad8 100644
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
@@ -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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.view.selectiontoolbar;
+package com.android.systemui.decor
-/**
- * @hide
- */
-parcelable SelectionContext;
+abstract class DecorProviderFactory {
+ abstract val providers: List<DecorProvider>
+ abstract val hasProviders: Boolean
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
new file mode 100644
index 0000000..9f8679c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.decor
+
+import android.annotation.IdRes
+import android.view.DisplayCutout
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import java.util.HashMap
+
+class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) {
+
+ private val layoutId: Int
+ get() {
+ return if (pos == DisplayCutout.BOUNDS_POSITION_LEFT ||
+ pos == DisplayCutout.BOUNDS_POSITION_TOP) {
+ R.layout.rounded_corners_top
+ } else {
+ R.layout.rounded_corners_bottom
+ }
+ }
+
+ val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup
+ private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap()
+
+ fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) {
+ val view = decorProvider.inflateView(layoutInflater, rootView, rotation)
+ viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
+ }
+
+ fun getView(@IdRes id: Int): View? {
+ val pair = viewProviderMap[id]
+ return pair?.first
+ }
+
+ fun removeView(@IdRes id: Int) {
+ val view = getView(id)
+ if (view != null) {
+ rootView.removeView(view)
+ viewProviderMap.remove(id)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
new file mode 100644
index 0000000..7afd7e0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.content.res.Resources
+import android.view.DisplayCutout
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Surface
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+@SysUISingleton
+class PrivacyDotDecorProviderFactory @Inject constructor(
+ @Main private val res: Resources
+) : DecorProviderFactory() {
+
+ private val isPrivacyDotEnabled: Boolean
+ get() = res.getBoolean(R.bool.config_enablePrivacyDot)
+
+ override val hasProviders: Boolean
+ get() = isPrivacyDotEnabled
+
+ override val providers: List<DecorProvider>
+ get() {
+ return if (hasProviders) {
+ listOf(
+ PrivacyDotCornerDecorProviderImpl(
+ viewId = R.id.privacy_dot_top_left_container,
+ alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+ alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+ layoutId = R.layout.privacy_dot_top_left),
+ PrivacyDotCornerDecorProviderImpl(
+ viewId = R.id.privacy_dot_top_right_container,
+ alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+ alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+ layoutId = R.layout.privacy_dot_top_right),
+ PrivacyDotCornerDecorProviderImpl(
+ viewId = R.id.privacy_dot_bottom_left_container,
+ alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+ alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+ layoutId = R.layout.privacy_dot_bottom_left),
+ PrivacyDotCornerDecorProviderImpl(
+ viewId = R.id.privacy_dot_bottom_right_container,
+ alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+ alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+ layoutId = R.layout.privacy_dot_bottom_right)
+ )
+ } else {
+ emptyList()
+ }
+ }
+}
+
+class PrivacyDotCornerDecorProviderImpl(
+ override val viewId: Int,
+ @DisplayCutout.BoundsPosition override val alignedBound1: Int,
+ @DisplayCutout.BoundsPosition override val alignedBound2: Int,
+ private val layoutId: Int
+) : CornerDecorProvider() {
+
+ override fun inflateView(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ @Surface.Rotation rotation: Int
+ ): View {
+ inflater.inflate(layoutId, parent, true)
+ return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 16ed1fb..3ee0cad 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -29,9 +29,12 @@
import androidx.lifecycle.ViewModelStore;
import com.android.internal.policy.PhoneWindow;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import java.util.concurrent.Executor;
@@ -53,6 +56,7 @@
// A controller for the dream overlay container view (which contains both the status bar and the
// content area).
private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
// A reference to the {@link Window} used to hold the dream overlay.
private Window mWindow;
@@ -68,19 +72,40 @@
private ViewModelStore mViewModelStore = new ViewModelStore();
+ private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+
+ private final KeyguardUpdateMonitorCallback mKeyguardCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onShadeExpandedChanged(boolean expanded) {
+ if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED
+ && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) {
+ return;
+ }
+
+ mLifecycleRegistry.setCurrentState(
+ expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+ }
+ };
+
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
- DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
+ DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mExecutor = executor;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
final DreamOverlayComponent component =
dreamOverlayComponentFactory.create(mViewModelStore, mHost);
mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
setCurrentState(Lifecycle.State.CREATED);
mLifecycleRegistry = component.getLifecycleRegistry();
+ mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
+ mDreamOverlayTouchMonitor.init();
}
private void setCurrentState(Lifecycle.State state) {
@@ -89,6 +114,7 @@
@Override
public void onDestroy() {
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
setCurrentState(Lifecycle.State.DESTROYED);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.removeView(mWindow.getDecorView());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index d5053a0..3d2f924 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,13 +16,18 @@
package com.android.systemui.dreams.dagger;
+import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+
import dagger.Module;
/**
* Dagger Module providing Communal-related functionality.
*/
-@Module(subcomponents = {
- DreamOverlayComponent.class,
-})
+@Module(includes = {
+ DreamTouchModule.class,
+ },
+ subcomponents = {
+ DreamOverlayComponent.class,
+ })
public interface DreamModule {
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index f0ab696..05ab901 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -25,6 +25,7 @@
import com.android.systemui.dreams.DreamOverlayContainerViewController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.complication.dagger.ComplicationModule;
+import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -64,4 +65,7 @@
/** Builds a {@link androidx.lifecycle.LifecycleOwner} */
LifecycleOwner getLifecycleOwner();
+
+ /** Builds a {@link DreamOverlayTouchMonitor} */
+ DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index b56aa2c..503817a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -22,6 +22,7 @@
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
@@ -33,6 +34,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -42,6 +44,7 @@
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.IntoSet;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
@@ -140,4 +143,18 @@
static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
return new LifecycleRegistry(lifecycleOwner);
}
+
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
+ return lifecycleOwner.getLifecycle();
+ }
+
+ // TODO: This stub should be removed once there is a {@link DreamTouchHandler}
+ // implementation present.
+ @Provides
+ @IntoSet
+ static DreamTouchHandler provideDreamTouchHandler() {
+ return session -> { };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
new file mode 100644
index 0000000..3e5efb2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dreams.touch;
+
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
+import com.android.systemui.shared.system.InputChannelCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamOverlayTouchMonitor} is responsible for monitoring touches and gestures over the
+ * dream overlay and redirecting them to a set of listeners. This monitor is in charge of figuring
+ * out when listeners are eligible for receiving touches and filtering the listener pool if
+ * touches are consumed.
+ */
+public class DreamOverlayTouchMonitor {
+ // This executor is used to protect {@code mActiveTouchSessions} from being modified
+ // concurrently. Any operation that adds or removes values should use this executor.
+ private final Executor mExecutor;
+ private final Lifecycle mLifecycle;
+
+ /**
+ * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
+ */
+ private ListenableFuture<DreamTouchHandler.TouchSession> push(
+ TouchSessionImpl touchSessionImpl) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ mExecutor.execute(() -> {
+ if (!mActiveTouchSessions.remove(touchSessionImpl)) {
+ completer.set(null);
+ return;
+ }
+
+ final TouchSessionImpl touchSession =
+ new TouchSessionImpl(this, touchSessionImpl);
+ mActiveTouchSessions.add(touchSession);
+ completer.set(touchSession);
+ });
+
+ return "DreamOverlayTouchMonitor::push";
+ });
+ }
+
+ /**
+ * Removes a {@link TouchSessionImpl} from receiving further updates.
+ */
+ private ListenableFuture<DreamTouchHandler.TouchSession> pop(
+ TouchSessionImpl touchSessionImpl) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ mExecutor.execute(() -> {
+ if (mActiveTouchSessions.remove(touchSessionImpl)) {
+ touchSessionImpl.onRemoved();
+
+ final TouchSessionImpl predecessor = touchSessionImpl.getPredecessor();
+
+ if (predecessor != null) {
+ mActiveTouchSessions.add(predecessor);
+ }
+
+ completer.set(predecessor);
+ }
+ });
+
+ return "DreamOverlayTouchMonitor::pop";
+ });
+ }
+
+ /**
+ * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for
+ * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners
+ * and provides the associated client with access to the monitor.
+ */
+ private static class TouchSessionImpl implements DreamTouchHandler.TouchSession {
+ private final HashSet<InputChannelCompat.InputEventListener> mEventListeners =
+ new HashSet<>();
+ private final HashSet<GestureDetector.OnGestureListener> mGestureListeners =
+ new HashSet<>();
+ private final HashSet<Callback> mCallbacks = new HashSet<>();
+
+ private final TouchSessionImpl mPredecessor;
+ private final DreamOverlayTouchMonitor mTouchMonitor;
+
+ TouchSessionImpl(DreamOverlayTouchMonitor touchMonitor, TouchSessionImpl predecessor) {
+ mPredecessor = predecessor;
+ mTouchMonitor = touchMonitor;
+ }
+
+ @Override
+ public void registerCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public boolean registerInputListener(
+ InputChannelCompat.InputEventListener inputEventListener) {
+ return mEventListeners.add(inputEventListener);
+ }
+
+ @Override
+ public boolean registerGestureListener(GestureDetector.OnGestureListener gestureListener) {
+ return mGestureListeners.add(gestureListener);
+ }
+
+ @Override
+ public ListenableFuture<DreamTouchHandler.TouchSession> push() {
+ return mTouchMonitor.push(this);
+ }
+
+ @Override
+ public ListenableFuture<DreamTouchHandler.TouchSession> pop() {
+ return mTouchMonitor.pop(this);
+ }
+
+ /**
+ * Returns the active listeners to receive touch events.
+ */
+ public Collection<InputChannelCompat.InputEventListener> getEventListeners() {
+ return mEventListeners;
+ }
+
+ /**
+ * Returns the active listeners to receive gesture events.
+ */
+ public Collection<GestureDetector.OnGestureListener> getGestureListeners() {
+ return mGestureListeners;
+ }
+
+ /**
+ * Returns the {@link TouchSessionImpl} that preceded this current session. This will
+ * become the new active session when this session is popped.
+ */
+ private TouchSessionImpl getPredecessor() {
+ return mPredecessor;
+ }
+
+ /**
+ * Called by the monitor when this session is removed.
+ */
+ private void onRemoved() {
+ mCallbacks.forEach(callback -> callback.onRemoved());
+ }
+ }
+
+ /**
+ * This lifecycle observer ensures touch monitoring only occurs while the overlay is "resumed".
+ * This concept is mapped over from the equivalent view definition: The {@link LifecycleOwner}
+ * will report the dream is not resumed when it is obscured (from the notification shade being
+ * expanded for example) or not active (such as when it is destroyed).
+ */
+ private final LifecycleObserver mLifecycleObserver = new DefaultLifecycleObserver() {
+ @Override
+ public void onResume(@NonNull LifecycleOwner owner) {
+ startMonitoring();
+ }
+
+ @Override
+ public void onPause(@NonNull LifecycleOwner owner) {
+ stopMonitoring();
+ }
+ };
+
+ /**
+ * When invoked, instantiates a new {@link InputSession} to monitor touch events.
+ */
+ private void startMonitoring() {
+ stopMonitoring();
+ mCurrentInputSession = mInputSessionFactory.create(
+ "dreamOverlay",
+ mInputEventListener,
+ mOnGestureListener,
+ true)
+ .getInputSession();
+ }
+
+ /**
+ * Destroys any active {@link InputSession}.
+ */
+ private void stopMonitoring() {
+ if (mCurrentInputSession == null) {
+ return;
+ }
+
+ mCurrentInputSession.dispose();
+ mCurrentInputSession = null;
+ }
+
+
+ private final HashSet<TouchSessionImpl> mActiveTouchSessions = new HashSet<>();
+ private final Collection<DreamTouchHandler> mHandlers;
+
+ private InputChannelCompat.InputEventListener mInputEventListener =
+ new InputChannelCompat.InputEventListener() {
+ @Override
+ public void onInputEvent(InputEvent ev) {
+ // No Active sessions are receiving touches. Create sessions for each listener
+ if (mActiveTouchSessions.isEmpty()) {
+ for (DreamTouchHandler handler : mHandlers) {
+ final TouchSessionImpl sessionStack =
+ new TouchSessionImpl(DreamOverlayTouchMonitor.this, null);
+ mActiveTouchSessions.add(sessionStack);
+ handler.onSessionStart(sessionStack);
+ }
+ }
+
+ // Find active sessions and invoke on InputEvent.
+ mActiveTouchSessions.stream()
+ .map(touchSessionStack -> touchSessionStack.getEventListeners())
+ .flatMap(Collection::stream)
+ .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
+ }
+ };
+
+ /**
+ * The {@link Evaluator} interface allows for callers to inspect a listener from the
+ * {@link android.view.GestureDetector.OnGestureListener} set. This helps reduce duplicated
+ * iteration loops over this set.
+ */
+ private interface Evaluator {
+ boolean evaluate(GestureDetector.OnGestureListener listener);
+ }
+
+ private GestureDetector.OnGestureListener mOnGestureListener =
+ new GestureDetector.OnGestureListener() {
+ private boolean evaluate(Evaluator evaluator) {
+ final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
+
+ // When a gesture is consumed, it is assumed that all touches for the current session
+ // should be directed only to those TouchSessions until those sessions are popped. All
+ // non-participating sessions are removed from receiving further updates with
+ // {@link DreamOverlayTouchMonitor#isolate}.
+ final boolean eventConsumed = mActiveTouchSessions.stream()
+ .map(touchSession -> {
+ boolean consume = touchSession.getGestureListeners()
+ .stream()
+ .map(listener -> evaluator.evaluate(listener))
+ .anyMatch(consumed -> consumed);
+
+ if (consume) {
+ consumingSessions.add(touchSession);
+ }
+ return consume;
+ }).anyMatch(consumed -> consumed);
+
+ if (eventConsumed) {
+ DreamOverlayTouchMonitor.this.isolate(consumingSessions);
+ }
+
+ return eventConsumed;
+ }
+
+ // This method is called for gesture events that cannot be consumed.
+ private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
+ mActiveTouchSessions.stream()
+ .map(touchSession -> touchSession.getGestureListeners())
+ .flatMap(Collection::stream)
+ .forEach(listener -> consumer.accept(listener));
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return evaluate(listener -> listener.onDown(e));
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ observe(listener -> listener.onLongPress(e));
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ observe(listener -> listener.onShowPress(e));
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return evaluate(listener -> listener.onSingleTapUp(e));
+ }
+ };
+
+ private InputSessionComponent.Factory mInputSessionFactory;
+ private InputSession mCurrentInputSession;
+
+ /**
+ * Designated constructor for {@link DreamOverlayTouchMonitor}
+ * @param executor This executor will be used for maintaining the active listener list to avoid
+ * concurrent modification.
+ * @param lifecycle {@link DreamOverlayTouchMonitor} will listen to this lifecycle to determine
+ * whether touch monitoring should be active.
+ * @param inputSessionFactory This factory will generate the {@link InputSession} requested by
+ * the monitor. Each session should be unique and valid when
+ * returned.
+ * @param handlers This set represents the {@link DreamTouchHandler} instances that will
+ * participate in touch handling.
+ */
+ @Inject
+ public DreamOverlayTouchMonitor(
+ @Main Executor executor,
+ Lifecycle lifecycle,
+ InputSessionComponent.Factory inputSessionFactory,
+ Set<DreamTouchHandler> handlers) {
+ mHandlers = handlers;
+ mInputSessionFactory = inputSessionFactory;
+ mExecutor = executor;
+ mLifecycle = lifecycle;
+ }
+
+ /**
+ * Initializes the monitor. should only be called once after creation.
+ */
+ public void init() {
+ mLifecycle.addObserver(mLifecycleObserver);
+ }
+
+ private void isolate(Set<TouchSessionImpl> sessions) {
+ Collection<TouchSessionImpl> removedSessions = mActiveTouchSessions.stream()
+ .filter(touchSession -> !sessions.contains(touchSession))
+ .collect(Collectors.toCollection(HashSet::new));
+
+ removedSessions.forEach(touchSession -> touchSession.onRemoved());
+
+ mActiveTouchSessions.removeAll(removedSessions);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
new file mode 100644
index 0000000..c73ff73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch;
+
+import android.view.GestureDetector;
+
+import com.android.systemui.shared.system.InputChannelCompat;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * The {@link DreamTouchHandler} interface provides a way for dream overlay components to observe
+ * touch events and gestures with the ability to intercept the latter. Touch interaction sequences
+ * are abstracted as sessions. A session represents the time of first
+ * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link DreamTouchHandler}
+ * stopping interception of gestures. If no gesture is intercepted, the session continues
+ * indefinitely. {@link DreamTouchHandler} have the ability to create a stack of sessions, which
+ * allows for motion logic to be captured in modal states.
+ */
+public interface DreamTouchHandler {
+ /**
+ * A touch session captures the interaction surface of a {@link DreamTouchHandler}. Clients
+ * register listeners as desired to participate in motion/gesture callbacks.
+ */
+ interface TouchSession {
+ interface Callback {
+ void onRemoved();
+ }
+
+ void registerCallback(Callback callback);
+
+ /**
+ * Adds a input event listener for the given session.
+ * @param inputEventListener
+ */
+ boolean registerInputListener(InputChannelCompat.InputEventListener inputEventListener);
+
+ /**
+ * Adds a gesture listener for the given session.
+ * @param gestureListener
+ */
+ boolean registerGestureListener(GestureDetector.OnGestureListener gestureListener);
+
+ /**
+ * Creates a new {@link TouchSession} that will receive any updates that would have been
+ * directed to this {@link TouchSession}.
+ * @return The future which will return a new {@link TouchSession} that will receive
+ * subsequent events. If the operation fails, {@code null} will be returned.
+ */
+ ListenableFuture<TouchSession> push();
+
+ /**
+ * Explicitly releases this {@link TouchSession}. The registered listeners will no longer
+ * receive any further updates.
+ * @return The future containing the {@link TouchSession} that will receive subsequent
+ * events. This session will be the direct predecessor of the popped session. {@code null}
+ * if the popped {@link TouchSession} was the initial session or has already been popped.
+ */
+ ListenableFuture<TouchSession> pop();
+ }
+
+ /**
+ * Informed a new touch session has begun. The first touch event will be delivered to any
+ * listener registered through
+ * {@link TouchSession#registerInputListener(InputChannelCompat.InputEventListener)} during this
+ * call. If there are no interactions with this touch session after this method returns, it will
+ * be dropped.
+ * @param session
+ */
+ void onSessionStart(TouchSession session);
+
+ /**
+ * Invoked when a session has ended. This will be invoked for every session completion, even
+ * those that are removed through {@link TouchSession#pop()}.
+ * @param session
+ */
+ default void onSessionEnd(TouchSession session) { }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
new file mode 100644
index 0000000..4382757
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.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.systemui.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link InputSession} encapsulates components behind input monitoring and handles their lifecycle.
+ * Sessions are meant to be disposable; actions such as exclusively capturing touch events is modal
+ * and destroying the sessions allows a reset. Additionally, {@link InputSession} is meant to have
+ * a single listener for input and gesture. Any broadcasting must be accomplished elsewhere.
+ */
+public class InputSession {
+ private final InputMonitorCompat mInputMonitor;
+ private final InputChannelCompat.InputEventReceiver mInputEventReceiver;
+ private final GestureDetector mGestureDetector;
+
+ /**
+ * Default session constructor.
+ * @param sessionName The session name that will be applied to the underlying
+ * {@link InputMonitorCompat}.
+ * @param inputEventListener A listener to receive input events.
+ * @param gestureListener A listener to receive gesture events.
+ * @param pilferOnGestureConsume Whether touch events should be pilfered after a gesture has
+ * been consumed.
+ */
+ @Inject
+ public InputSession(@Named(INPUT_SESSION_NAME) String sessionName,
+ InputChannelCompat.InputEventListener inputEventListener,
+ GestureDetector.OnGestureListener gestureListener,
+ @Named(PILFER_ON_GESTURE_CONSUME) boolean pilferOnGestureConsume) {
+ mInputMonitor = new InputMonitorCompat(sessionName, Display.DEFAULT_DISPLAY);
+ mGestureDetector = new GestureDetector(gestureListener);
+
+ mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
+ Choreographer.getInstance(),
+ ev -> {
+ // Process event. Since sometimes input may be a prerequisite for some
+ // gesture logic, process input first.
+ inputEventListener.onInputEvent(ev);
+
+ if (ev instanceof MotionEvent
+ && mGestureDetector.onTouchEvent((MotionEvent) ev)
+ && pilferOnGestureConsume) {
+ mInputMonitor.pilferPointers();
+ }
+ });
+ }
+
+ /**
+ * Destroys the {@link InputSession}, removing any component from listening to future touch
+ * events.
+ */
+ public void dispose() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ }
+
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
new file mode 100644
index 0000000..7b77b59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch.dagger;
+
+import dagger.Module;
+
+/**
+ * {@link DreamTouchModule} encapsulates dream touch-related components.
+ */
+@Module(subcomponents = {
+ InputSessionComponent.class,
+})
+public interface DreamTouchModule {
+ String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
+ String PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME";
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
new file mode 100644
index 0000000..ad59a2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
@@ -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.dreams.touch.dagger;
+
+import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+
+import android.view.GestureDetector;
+
+import com.android.systemui.dreams.touch.InputSession;
+import com.android.systemui.shared.system.InputChannelCompat;
+
+import javax.inject.Named;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * {@link InputSessionComponent} generates {@link InputSession} with specific instances bound for
+ * the session name and whether touches should be pilfered when consumed.
+ */
+@Subcomponent
+public interface InputSessionComponent {
+ /**
+ * Generates {@link InputSessionComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ InputSessionComponent create(@Named(INPUT_SESSION_NAME) @BindsInstance String name,
+ @BindsInstance InputChannelCompat.InputEventListener inputEventListener,
+ @BindsInstance GestureDetector.OnGestureListener gestureListener,
+ @Named(PILFER_ON_GESTURE_CONSUME) @BindsInstance boolean pilferOnGestureConsume);
+ }
+
+ /** */
+ InputSession getInputSession();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index e24df30..97edf3b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -41,7 +41,7 @@
/***************************************/
// 100 - notification
public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
- new BooleanFlag(101, false);
+ new BooleanFlag(101, true);
public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
new BooleanFlag(103, false);
@@ -55,6 +55,9 @@
public static final BooleanFlag NSSL_DEBUG_REMOVE_ANIMATION =
new BooleanFlag(106, false);
+ public static final BooleanFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE =
+ new BooleanFlag(107, false);
+
/***************************************/
// 200 - keyguard/lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 24ad75d..b337183 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
@@ -92,8 +93,11 @@
// Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
// redaction switched on.
+ final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ String contentDescription = dpm.getString(
+ WORK_LOCK_ACCESSIBILITY, () -> getString(R.string.accessibility_desc_work_lock));
final View blankView = new View(this);
- blankView.setContentDescription(getString(R.string.accessibility_desc_work_lock));
+ blankView.setContentDescription(contentDescription);
blankView.setBackgroundColor(getPrimaryColor());
setContentView(blankView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
new file mode 100644
index 0000000..0656f5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Track Session InstanceIds to be used for metrics logging to correlate logs in the same
+ * session. Can be used across processes via StatusBarManagerService#registerSessionListener
+ */
+@SysUISingleton
+public class SessionTracker extends CoreStartable {
+ private static final String TAG = "SessionTracker";
+ private static final boolean DEBUG = false;
+
+ // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
+ private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
+
+ private final IStatusBarService mStatusBarManagerService;
+ private final AuthController mAuthController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardStateController mKeyguardStateController;
+ private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
+
+ private boolean mKeyguardSessionStarted;
+
+ @Inject
+ public SessionTracker(
+ Context context,
+ IStatusBarService statusBarService,
+ AuthController authController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController
+ ) {
+ super(context);
+ mStatusBarManagerService = statusBarService;
+ mAuthController = authController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ }
+
+ @Override
+ public void start() {
+ mAuthController.addCallback(mAuthControllerCallback);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
+
+ mKeyguardSessionStarted = mKeyguardStateController.isShowing();
+ if (mKeyguardSessionStarted) {
+ startSession(SESSION_KEYGUARD);
+ }
+ }
+
+ /**
+ * Get the session ID associated with the passed session type.
+ */
+ public @Nullable InstanceId getSessionId(int type) {
+ return mSessionToInstanceId.getOrDefault(type, null);
+ }
+
+ private void startSession(int type) {
+ if (mSessionToInstanceId.getOrDefault(type, null) != null) {
+ Log.e(TAG, "session [" + getString(type) + "] was already started");
+ return;
+ }
+
+ final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
+ mSessionToInstanceId.put(type, instanceId);
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
+ }
+ mStatusBarManagerService.onSessionStarted(type, instanceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to send onSessionStarted for session="
+ + "[" + getString(type) + "]", e);
+ }
+ }
+
+ private void endSession(int type) {
+ if (mSessionToInstanceId.getOrDefault(type, null) == null) {
+ Log.e(TAG, "session [" + getString(type) + "] was not started");
+ return;
+ }
+
+ final InstanceId instanceId = mSessionToInstanceId.get(type);
+ mSessionToInstanceId.put(type, null);
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
+ }
+ mStatusBarManagerService.onSessionEnded(type, instanceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to send onSessionEnded for session="
+ + "[" + getString(type) + "]", e);
+ }
+ }
+
+ public KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onStartedGoingToSleep(int why) {
+ // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
+ // before the WakefulnessLifecycle
+ if (mKeyguardSessionStarted) {
+ return;
+ }
+
+ mKeyguardSessionStarted = true;
+ startSession(SESSION_KEYGUARD);
+ }
+ };
+
+
+ public KeyguardStateController.Callback mKeyguardStateCallback =
+ new KeyguardStateController.Callback() {
+ public void onKeyguardShowingChanged() {
+ boolean wasSessionStarted = mKeyguardSessionStarted;
+ boolean keyguardShowing = mKeyguardStateController.isShowing();
+ if (keyguardShowing && !wasSessionStarted) {
+ mKeyguardSessionStarted = true;
+ startSession(SESSION_KEYGUARD);
+ } else if (!keyguardShowing && wasSessionStarted) {
+ mKeyguardSessionStarted = false;
+ endSession(SESSION_KEYGUARD);
+ }
+ }
+ };
+
+ public AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onBiometricPromptShown() {
+ startSession(SESSION_BIOMETRIC_PROMPT);
+ }
+
+ @Override
+ public void onBiometricPromptDismissed() {
+ endSession(SESSION_BIOMETRIC_PROMPT);
+ }
+ };
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ for (int session : ALL_SESSIONS) {
+ pw.println(" " + getString(session)
+ + " instanceId=" + mSessionToInstanceId.get(session));
+ }
+ }
+
+ /**
+ * @return the string representation of a SINGLE SessionFlag. Combined SessionFlags will be
+ * considered unknown.
+ */
+ public static String getString(int sessionType) {
+ if (sessionType == SESSION_KEYGUARD) {
+ return "KEYGUARD";
+ } else if (sessionType == SESSION_BIOMETRIC_PROMPT) {
+ return "BIOMETRIC_PROMPT";
+ }
+
+ return "unknownType=" + sessionType;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 878f753..9e17c12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -16,6 +16,31 @@
package com.android.systemui.qs;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -24,6 +49,7 @@
import android.app.AlertDialog;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -75,6 +101,7 @@
private final TextView mFooterText;
private final ImageView mPrimaryFooterIcon;
private final Context mContext;
+ private final DevicePolicyManager mDpm;
private final Callback mCallback = new Callback();
private final SecurityController mSecurityController;
private final ActivityStarter mActivityStarter;
@@ -102,6 +129,7 @@
mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
mFooterIconId = R.drawable.ic_info_outline;
mContext = rootView.getContext();
+ mDpm = rootView.getContext().getSystemService(DevicePolicyManager.class);
mMainHandler = mainHandler;
mActivityStarter = activityStarter;
mSecurityController = securityController;
@@ -254,87 +282,175 @@
return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
}
if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
- if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
- if (organizationName == null) {
- return mContext.getString(
- R.string.quick_settings_disclosure_management_monitoring);
- }
- return mContext.getString(
+ return getManagedDeviceFooterText(hasCACerts, hasCACertsInWorkProfile,
+ isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile, organizationName);
+ }
+ return getManagedAndPersonalProfileFooterText(hasWorkProfile, hasCACerts,
+ hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile,
+ workProfileOrganizationName, isProfileOwnerOfOrganizationOwnedDevice,
+ isWorkProfileOn);
+ }
+
+ private String getManagedDeviceFooterText(
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
+ if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
+ return getManagedDeviceMonitoringText(organizationName);
+ }
+ if (vpnName != null || vpnNameWorkProfile != null) {
+ return getManagedDeviceVpnText(vpnName, vpnNameWorkProfile, organizationName);
+ }
+ return getMangedDeviceGeneralText(organizationName);
+ }
+
+ private String getManagedDeviceMonitoringText(CharSequence organizationName) {
+ if (organizationName == null) {
+ return mDpm.getString(
+ QS_MSG_MANAGEMENT_MONITORING,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_management_monitoring));
+ }
+ return mDpm.getString(
+ QS_MSG_NAMED_MANAGEMENT_MONITORING,
+ () -> mContext.getString(
R.string.quick_settings_disclosure_named_management_monitoring,
- organizationName);
+ organizationName),
+ organizationName);
+ }
+
+ private String getManagedDeviceVpnText(
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ if (organizationName == null) {
+ return mDpm.getString(
+ QS_MSG_MANAGEMENT_MULTIPLE_VPNS,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_management_vpns));
}
- if (vpnName != null && vpnNameWorkProfile != null) {
- if (organizationName == null) {
- return mContext.getString(R.string.quick_settings_disclosure_management_vpns);
- }
- return mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
- organizationName);
- }
- if (vpnName != null || vpnNameWorkProfile != null) {
- if (organizationName == null) {
- return mContext.getString(
+ return mDpm.getString(
+ QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management_vpns,
+ organizationName),
+ organizationName);
+ }
+ String name = vpnName != null ? vpnName : vpnNameWorkProfile;
+ if (organizationName == null) {
+ return mDpm.getString(
+ QS_MSG_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(
R.string.quick_settings_disclosure_management_named_vpn,
- vpnName != null ? vpnName : vpnNameWorkProfile);
- }
- return mContext.getString(
+ name),
+ name);
+ }
+ return mDpm.getString(
+ QS_MSG_NAMED_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(
R.string.quick_settings_disclosure_named_management_named_vpn,
organizationName,
- vpnName != null ? vpnName : vpnNameWorkProfile);
- }
- if (organizationName == null) {
- return mContext.getString(R.string.quick_settings_disclosure_management);
- }
- if (isFinancedDevice()) {
- return mContext.getString(
- R.string.quick_settings_financed_disclosure_named_management,
- organizationName);
- } else {
- return mContext.getString(R.string.quick_settings_disclosure_named_management,
- organizationName);
- }
- } // end if(isDeviceManaged)
+ name),
+ organizationName,
+ name);
+ }
+
+ private String getMangedDeviceGeneralText(CharSequence organizationName) {
+ if (organizationName == null) {
+ return mDpm.getString(
+ QS_MSG_MANAGEMENT,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_management));
+ }
+ if (isFinancedDevice()) {
+ return mContext.getString(
+ R.string.quick_settings_financed_disclosure_named_management,
+ organizationName);
+ } else {
+ return mDpm.getString(
+ QS_MSG_NAMED_MANAGEMENT,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management,
+ organizationName),
+ organizationName);
+ }
+ }
+
+ private String getManagedAndPersonalProfileFooterText(boolean hasWorkProfile,
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence workProfileOrganizationName,
+ boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isWorkProfileOn) {
+ if (hasCACerts || (hasCACertsInWorkProfile && isWorkProfileOn)) {
+ return getMonitoringText(
+ hasCACerts, hasCACertsInWorkProfile, workProfileOrganizationName,
+ isWorkProfileOn);
+ }
+ if (vpnName != null || (vpnNameWorkProfile != null && isWorkProfileOn)) {
+ return getVpnText(hasWorkProfile, vpnName, vpnNameWorkProfile, isWorkProfileOn);
+ }
+ if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
+ return getManagedProfileNetworkActivityText();
+ }
+ if (isProfileOwnerOfOrganizationOwnedDevice) {
+ return getMangedDeviceGeneralText(workProfileOrganizationName);
+ }
+ return null;
+ }
+
+ private String getMonitoringText(boolean hasCACerts, boolean hasCACertsInWorkProfile,
+ CharSequence workProfileOrganizationName, boolean isWorkProfileOn) {
if (hasCACertsInWorkProfile && isWorkProfileOn) {
if (workProfileOrganizationName == null) {
- return mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_monitoring);
+ return mDpm.getString(
+ QS_MSG_WORK_PROFILE_MONITORING,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring));
}
- return mContext.getString(
- R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ return mDpm.getString(
+ QS_MSG_NAMED_WORK_PROFILE_MONITORING,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ workProfileOrganizationName),
workProfileOrganizationName);
}
if (hasCACerts) {
return mContext.getString(R.string.quick_settings_disclosure_monitoring);
}
+ return null;
+ }
+
+ private String getVpnText(boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile,
+ boolean isWorkProfileOn) {
if (vpnName != null && vpnNameWorkProfile != null) {
return mContext.getString(R.string.quick_settings_disclosure_vpns);
}
if (vpnNameWorkProfile != null && isWorkProfileOn) {
- return mContext.getString(R.string.quick_settings_disclosure_managed_profile_named_vpn,
+ return mDpm.getString(
+ QS_MSG_WORK_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_named_vpn,
+ vpnNameWorkProfile),
vpnNameWorkProfile);
}
if (vpnName != null) {
if (hasWorkProfile) {
- return mContext.getString(
- R.string.quick_settings_disclosure_personal_profile_named_vpn,
+ return mDpm.getString(
+ QS_MSG_PERSONAL_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_personal_profile_named_vpn,
+ vpnName),
vpnName);
}
return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
vpnName);
}
- if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
- return mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_network_activity);
- }
- if (isProfileOwnerOfOrganizationOwnedDevice) {
- if (workProfileOrganizationName == null) {
- return mContext.getString(R.string.quick_settings_disclosure_management);
- }
- return mContext.getString(R.string.quick_settings_disclosure_named_management,
- workProfileOrganizationName);
- }
return null;
}
+ private String getManagedProfileNetworkActivityText() {
+ return mDpm.getString(
+ QS_MSG_WORK_PROFILE_NETWORK,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_network_activity));
+ }
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
@@ -494,7 +610,9 @@
@VisibleForTesting
String getSettingsButton() {
- return mContext.getString(R.string.monitoring_button_view_policies);
+ return mDpm.getString(
+ QS_DIALOG_VIEW_POLICIES,
+ () -> mContext.getString(R.string.monitoring_button_view_policies));
}
private String getPositiveButton() {
@@ -520,11 +638,17 @@
return mContext.getString(R.string.monitoring_financed_description_named_management,
organizationName, organizationName);
} else {
- return mContext.getString(
- R.string.monitoring_description_named_management, organizationName);
+ return mDpm.getString(
+ QS_DIALOG_NAMED_MANAGEMENT,
+ () -> mContext.getString(
+ R.string.monitoring_description_named_management,
+ organizationName),
+ organizationName);
}
}
- return mContext.getString(R.string.monitoring_description_management);
+ return mDpm.getString(
+ QS_DIALOG_MANAGEMENT,
+ () -> mContext.getString(R.string.monitoring_description_management));
}
@Nullable
@@ -532,11 +656,16 @@
boolean hasCACertsInWorkProfile) {
if (!(hasCACerts || hasCACertsInWorkProfile)) return null;
if (isDeviceManaged) {
- return mContext.getString(R.string.monitoring_description_management_ca_certificate);
+ return mDpm.getString(
+ QS_DIALOG_MANAGEMENT_CA_CERT,
+ () -> mContext.getString(
+ R.string.monitoring_description_management_ca_certificate));
}
if (hasCACertsInWorkProfile) {
- return mContext.getString(
- R.string.monitoring_description_managed_profile_ca_certificate);
+ return mDpm.getString(
+ QS_DIALOG_WORK_PROFILE_CA_CERT,
+ () -> mContext.getString(
+ R.string.monitoring_description_managed_profile_ca_certificate));
}
return mContext.getString(R.string.monitoring_description_ca_certificate);
}
@@ -546,10 +675,15 @@
boolean isNetworkLoggingEnabled) {
if (!isNetworkLoggingEnabled) return null;
if (isDeviceManaged) {
- return mContext.getString(R.string.monitoring_description_management_network_logging);
+ return mDpm.getString(
+ QS_DIALOG_MANAGEMENT_NETWORK,
+ () -> mContext.getString(
+ R.string.monitoring_description_management_network_logging));
} else {
- return mContext.getString(
- R.string.monitoring_description_managed_profile_network_logging);
+ return mDpm.getString(
+ QS_DIALOG_WORK_PROFILE_NETWORK,
+ () -> mContext.getString(
+ R.string.monitoring_description_managed_profile_network_logging));
}
}
@@ -560,23 +694,46 @@
final SpannableStringBuilder message = new SpannableStringBuilder();
if (isDeviceManaged) {
if (vpnName != null && vpnNameWorkProfile != null) {
- message.append(mContext.getString(R.string.monitoring_description_two_named_vpns,
- vpnName, vpnNameWorkProfile));
+ String namedVpns = mDpm.getString(
+ QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_two_named_vpns,
+ vpnName, vpnNameWorkProfile),
+ vpnName, vpnNameWorkProfile);
+ message.append(namedVpns);
} else {
- message.append(mContext.getString(R.string.monitoring_description_named_vpn,
- vpnName != null ? vpnName : vpnNameWorkProfile));
+ String name = vpnName != null ? vpnName : vpnNameWorkProfile;
+ String namedVp = mDpm.getString(
+ QS_DIALOG_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(R.string.monitoring_description_named_vpn, name),
+ name);
+ message.append(namedVp);
}
} else {
if (vpnName != null && vpnNameWorkProfile != null) {
- message.append(mContext.getString(R.string.monitoring_description_two_named_vpns,
- vpnName, vpnNameWorkProfile));
+ String namedVpns = mDpm.getString(
+ QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_two_named_vpns,
+ vpnName, vpnNameWorkProfile),
+ vpnName, vpnNameWorkProfile);
+ message.append(namedVpns);
} else if (vpnNameWorkProfile != null) {
- message.append(mContext.getString(
- R.string.monitoring_description_managed_profile_named_vpn,
- vpnNameWorkProfile));
+ String namedVpn = mDpm.getString(
+ QS_DIALOG_WORK_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_managed_profile_named_vpn,
+ vpnNameWorkProfile),
+ vpnNameWorkProfile);
+ message.append(namedVpn);
} else if (hasWorkProfile) {
- message.append(mContext.getString(
- R.string.monitoring_description_personal_profile_named_vpn, vpnName));
+ String namedVpn = mDpm.getString(
+ QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_personal_profile_named_vpn,
+ vpnName),
+ vpnName);
+ message.append(namedVpn);
} else {
message.append(mContext.getString(R.string.monitoring_description_named_vpn,
vpnName));
@@ -594,7 +751,9 @@
return mContext.getString(R.string.monitoring_title_financed_device,
deviceOwnerOrganization);
} else {
- return mContext.getString(R.string.monitoring_title_device_owned);
+ return mDpm.getString(
+ QS_DIALOG_MANAGEMENT_TITLE,
+ () -> mContext.getString(R.string.monitoring_title_device_owned));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 8df8c63..4279b62 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -16,6 +16,9 @@
package com.android.systemui.qs.tiles;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL;
+
+import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
@@ -100,7 +103,9 @@
@Override
public CharSequence getTileLabel() {
- return mContext.getString(R.string.quick_settings_work_mode_label);
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ return dpm.getString(QS_WORK_PROFILE_LABEL,
+ () -> mContext.getString(R.string.quick_settings_work_mode_label));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index 6c01f0e..dec5afd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -21,7 +21,6 @@
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -129,7 +128,6 @@
iconParams.setMarginStart(paddingHorizontal);
iconParams.setMarginEnd(paddingHorizontal);
}
- mTextView.setVisibility(hasText ? View.VISIBLE : View.GONE);
mIconView.setLayoutParams(iconParams);
mTextView.setLayoutParams(textParams);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6335f88..a3f0a6dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -343,7 +344,9 @@
private CharSequence getDisclosureText(@Nullable CharSequence organizationName) {
final Resources packageResources = mContext.getResources();
if (organizationName == null) {
- return packageResources.getText(R.string.do_disclosure_generic);
+ return mDevicePolicyManager.getString(
+ KEYGUARD_MANAGEMENT_DISCLOSURE,
+ () -> packageResources.getString(R.string.do_disclosure_generic));
} else if (mDevicePolicyManager.isDeviceManaged()
&& mDevicePolicyManager.getDeviceOwnerType(
mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
@@ -351,7 +354,10 @@
return packageResources.getString(R.string.do_financed_disclosure_with_name,
organizationName);
} else {
- return packageResources.getString(R.string.do_disclosure_with_name,
+ return mDevicePolicyManager.getString(
+ KEYGUARD_MANAGEMENT_DISCLOSURE,
+ () -> packageResources.getString(
+ R.string.do_disclosure_with_name, organizationName),
organizationName);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 1432f78..f6a55e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -21,6 +21,7 @@
import android.widget.Toast
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.util.Compile
import javax.inject.Inject
class NotifPipelineFlags @Inject constructor(
@@ -31,8 +32,16 @@
if (!isNewPipelineEnabled()) {
return true
}
- Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", Exception())
- Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
+
+ if (Compile.IS_DEBUG) {
+ Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
+ }
+ if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) {
+ throw RuntimeException("Old pipeline code running with new pipeline enabled")
+ } else {
+ Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled",
+ Exception())
+ }
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 120c722..0f21a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -359,6 +359,13 @@
private void buildList() {
Trace.beginSection("ShadeListBuilder.buildList");
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
+
+ if (!mNotifStabilityManager.isPipelineRunAllowed()) {
+ mLogger.logPipelineRunSuppressed();
+ Trace.endSection();
+ return;
+ }
+
mPipelineState.setState(STATE_BUILD_STARTED);
// Step 1: Reset notification states
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 327876c..fcc2b26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,17 +57,23 @@
*/
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
-public class VisualStabilityCoordinator implements Coordinator, Dumpable {
+public class VisualStabilityCoordinator implements Coordinator, Dumpable,
+ NotifPanelEventSource.Callbacks {
private final DelayableExecutor mDelayableExecutor;
- private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final StatusBarStateController mStatusBarStateController;
private final HeadsUpManager mHeadsUpManager;
+ private final NotifPanelEventSource mNotifPanelEventSource;
+ private final StatusBarStateController mStatusBarStateController;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mScreenOn;
private boolean mPanelExpanded;
private boolean mPulsing;
+ private boolean mNotifPanelCollapsing;
+ private boolean mNotifPanelLaunchingActivity;
+ private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
+ private boolean mIsSuppressingPipelineRun = false;
private boolean mIsSuppressingGroupChange = false;
private final Set<String> mEntriesWithSuppressedSectionChange = new HashSet<>();
private boolean mIsSuppressingEntryReorder = false;
@@ -81,16 +88,17 @@
@Inject
public VisualStabilityCoordinator(
+ DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
- WakefulnessLifecycle wakefulnessLifecycle,
+ NotifPanelEventSource notifPanelEventSource,
StatusBarStateController statusBarStateController,
- DelayableExecutor delayableExecutor
- ) {
+ WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
+ mNotifPanelEventSource = notifPanelEventSource;
dumpManager.registerDumpable(this);
}
@@ -103,6 +111,7 @@
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
+ mNotifPanelEventSource.registerCallbacks(this);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -112,12 +121,19 @@
new NotifStabilityManager("VisualStabilityCoordinator") {
@Override
public void onBeginRun() {
+ mIsSuppressingPipelineRun = false;
mIsSuppressingGroupChange = false;
mEntriesWithSuppressedSectionChange.clear();
mIsSuppressingEntryReorder = false;
}
@Override
+ public boolean isPipelineRunAllowed() {
+ mIsSuppressingPipelineRun |= !mPipelineRunAllowed;
+ return mPipelineRunAllowed;
+ }
+
+ @Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
@@ -154,9 +170,12 @@
};
private void updateAllowedStates() {
+ mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
mReorderingAllowed = isReorderingAllowed();
- if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange()
- || mIsSuppressingEntryReorder)) {
+ if ((mPipelineRunAllowed && mIsSuppressingPipelineRun)
+ || (mReorderingAllowed && (mIsSuppressingGroupChange
+ || isSuppressingSectionChange()
+ || mIsSuppressingEntryReorder))) {
mNotifStabilityManager.invalidateList();
}
}
@@ -165,6 +184,10 @@
return !mEntriesWithSuppressedSectionChange.isEmpty();
}
+ private boolean isPanelCollapsingOrLaunchingActivity() {
+ return mNotifPanelCollapsing || mNotifPanelLaunchingActivity;
+ }
+
private boolean isReorderingAllowed() {
return (!mScreenOn || !mPanelExpanded) && !mPulsing;
}
@@ -248,4 +271,16 @@
pw.println(" " + key);
}
}
+
+ @Override
+ public void onPanelCollapsingChanged(boolean isCollapsing) {
+ mNotifPanelCollapsing = isCollapsing;
+ updateAllowedStates();
+ }
+
+ @Override
+ public void onLaunchingActivityChanged(boolean isLaunchingActivity) {
+ mNotifPanelLaunchingActivity = isLaunchingActivity;
+ updateAllowedStates();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index ba3e855..f8bf85f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -306,6 +306,9 @@
}
}
}
+
+ fun logPipelineRunSuppressed() =
+ buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
}
private const val TAG = "ShadeListBuilder"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
index 60f557c..4464498 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
@@ -27,6 +27,16 @@
*/
abstract class NotifStabilityManager protected constructor(name: String) :
Pluggable<NotifStabilityManager>(name) {
+
+ /**
+ * Called prior to running the pipeline to suppress any visual changes. Ex: collapse animation
+ * is playing, moving stuff around simultaneously will look janky.
+ *
+ * Note: this is invoked *before* [onBeginRun], so that implementors can reference state
+ * maintained from a previous run.
+ */
+ abstract fun isPipelineRunAllowed(): Boolean
+
/**
* Called at the beginning of every pipeline run to perform any necessary cleanup from the
* previous run.
@@ -76,6 +86,7 @@
/** The default, no-op instance of the stability manager which always allows all changes */
object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabilityManager") {
+ override fun isPipelineRunAllowed(): Boolean = true
override fun onBeginRun() {}
override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true
override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt
new file mode 100644
index 0000000..920d3c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
+import com.android.systemui.util.ListenerSet
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+
+/** Provides certain notification panel events. */
+interface NotifPanelEventSource {
+
+ /** Registers callbacks to be invoked when notification panel events occur. */
+ fun registerCallbacks(callbacks: Callbacks)
+
+ /** Unregisters callbacks previously registered via [.registerCallbacks] */
+ fun unregisterCallbacks(callbacks: Callbacks)
+
+ /** Callbacks for certain notification panel events. */
+ interface Callbacks {
+
+ /** Invoked when the notification panel starts or stops collapsing. */
+ fun onPanelCollapsingChanged(isCollapsing: Boolean)
+
+ /**
+ * Invoked when the notification panel starts or stops launching an [android.app.Activity].
+ */
+ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+ }
+}
+
+@Module
+abstract class NotifPanelEventSourceModule {
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindEventSource(manager: NotifPanelEventSourceManager): NotifPanelEventSource
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ fun provideManager(): NotifPanelEventSourceManager = NotifPanelEventSourceManagerImpl()
+ }
+}
+
+@Module
+object StatusBarNotifPanelEventSourceModule {
+ @JvmStatic
+ @Provides
+ @IntoSet
+ @StatusBarScope
+ fun bindStartable(
+ manager: NotifPanelEventSourceManager,
+ notifPanelController: NotificationPanelViewController
+ ): StatusBarComponent.Startable =
+ EventSourceStatusBarStartableImpl(manager, notifPanelController)
+}
+
+/**
+ * Management layer that bridges [SysUiSingleton] and [StatusBarScope]. Necessary because code that
+ * wants to listen to [NotifPanelEventSource] lives in [SysUiSingleton], but the events themselves
+ * come from [NotificationPanelViewController] in [StatusBarScope].
+ */
+interface NotifPanelEventSourceManager : NotifPanelEventSource {
+ var eventSource: NotifPanelEventSource?
+}
+
+private class NotifPanelEventSourceManagerImpl
+ : NotifPanelEventSourceManager, NotifPanelEventSource.Callbacks {
+
+ private val callbackSet = ListenerSet<NotifPanelEventSource.Callbacks>()
+
+ override var eventSource: NotifPanelEventSource? = null
+ set(value) {
+ field?.unregisterCallbacks(this)
+ value?.registerCallbacks(this)
+ field = value
+ }
+
+ override fun registerCallbacks(callbacks: NotifPanelEventSource.Callbacks) {
+ callbackSet.addIfAbsent(callbacks)
+ }
+
+ override fun unregisterCallbacks(callbacks: NotifPanelEventSource.Callbacks) {
+ callbackSet.remove(callbacks)
+ }
+
+ override fun onPanelCollapsingChanged(isCollapsing: Boolean) {
+ callbackSet.forEach { it.onPanelCollapsingChanged(isCollapsing) }
+ }
+
+ override fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+ callbackSet.forEach { it.onLaunchingActivityChanged(isLaunchingActivity) }
+ }
+}
+
+private class EventSourceStatusBarStartableImpl(
+ private val manager: NotifPanelEventSourceManager,
+ private val notifPanelController: NotificationPanelViewController
+) : StatusBarComponent.Startable {
+
+ override fun start() {
+ manager.eventSource = notifPanelController
+ }
+
+ override fun stop() {
+ manager.eventSource = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 05c40b2..45a9092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSourceModule;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -101,8 +102,9 @@
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@Module(includes = {
+ CoordinatorsModule.class,
+ NotifPanelEventSourceModule.class,
NotificationSectionHeadersModule.class,
- CoordinatorsModule.class
})
public interface NotificationsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index ffec367..27610b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -50,15 +50,17 @@
private final NotificationMessagingUtil mNotificationMessagingUtil;
private final Map<NotificationEntry, CancellationSignal> mOngoingBindCallbacks =
new ArrayMap<>();
+ private final HeadsUpViewBinderLogger mLogger;
private NotificationPresenter mNotificationPresenter;
@Inject
HeadsUpViewBinder(
NotificationMessagingUtil notificationMessagingUtil,
- RowContentBindStage bindStage) {
+ RowContentBindStage bindStage, HeadsUpViewBinderLogger logger) {
mNotificationMessagingUtil = notificationMessagingUtil;
mStage = bindStage;
+ mLogger = logger;
}
/**
@@ -81,12 +83,14 @@
params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
CancellationSignal signal = mStage.requestRebind(entry, en -> {
+ mLogger.entryBoundSuccessfully(entry.getKey());
en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight());
if (callback != null) {
callback.onBindFinished(en);
}
});
abortBindCallback(entry);
+ mLogger.startBindingHun(entry.getKey());
mOngoingBindCallbacks.put(entry, signal);
}
@@ -97,6 +101,7 @@
public void abortBindCallback(NotificationEntry entry) {
CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry);
if (ongoingBindCallback != null) {
+ mLogger.currentOngoingBindingAborted(entry.getKey());
ongoingBindCallback.cancel();
}
}
@@ -107,6 +112,7 @@
public void unbindHeadsUpView(NotificationEntry entry) {
abortBindCallback(entry);
mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
- mStage.requestRebind(entry, null);
+ mLogger.entryContentViewMarkedFreeable(entry.getKey());
+ mStage.requestRebind(entry, e -> mLogger.entryUnbound(e.getKey()));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
new file mode 100644
index 0000000..06651f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -0,0 +1,49 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val buffer: LogBuffer) {
+ fun startBindingHun(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "start binding heads up entry $str1 "
+ })
+ }
+
+ fun currentOngoingBindingAborted(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "aborted potential ongoing heads up entry binding $str1 "
+ })
+ }
+
+ fun entryBoundSuccessfully(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "heads up entry bound successfully $str1 "
+ })
+ }
+
+ fun entryUnbound(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "heads up entry unbound successfully $str1 "
+ })
+ }
+
+ fun entryContentViewMarkedFreeable(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "start unbinding heads up entry $str1 "
+ })
+ }
+}
+const val TAG = "HeadsUpViewBinder"
\ No newline at end of file
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 4283343..eff8af0 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
@@ -305,7 +305,7 @@
return true;
}
};
-
+ private NotificationStackScrollLogger mLogger;
private StatusBar mStatusBar;
private int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
@@ -658,6 +658,10 @@
return 0f;
}
+ protected void setLogger(NotificationStackScrollLogger logger) {
+ mLogger = logger;
+ }
+
public float getNotificationSquishinessFraction() {
return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
}
@@ -736,6 +740,21 @@
}
}
+ private void logHunSkippedForUnexpectedState(String key, boolean expected, boolean actual) {
+ if (mLogger == null) return;
+ mLogger.hunSkippedForUnexpectedState(key, expected, actual);
+ }
+
+ private void logHunAnimationSkipped(String key, String reason) {
+ if (mLogger == null) return;
+ mLogger.hunAnimationSkipped(key, reason);
+ }
+
+ private void logHunAnimationEventAdded(String key, int type) {
+ if (mLogger == null) return;
+ mLogger.hunAnimationEventAdded(key, type);
+ }
+
private void onDrawDebug(Canvas canvas) {
if (mDebugTextUsedYPositions == null) {
mDebugTextUsedYPositions = new HashSet<>();
@@ -3100,10 +3119,12 @@
private void generateHeadsUpAnimationEvents() {
for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
ExpandableNotificationRow row = eventPair.first;
+ String key = row.getEntry().getKey();
boolean isHeadsUp = eventPair.second;
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
+ logHunSkippedForUnexpectedState(key, isHeadsUp, row.isHeadsUp());
continue;
}
int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
@@ -3121,6 +3142,7 @@
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
+ logHunAnimationSkipped(key, "row is child in group");
continue;
}
} else {
@@ -3128,6 +3150,7 @@
if (viewState == null) {
// A view state was never generated for this view, so we don't need to animate
// this. This may happen with notification children.
+ logHunAnimationSkipped(key, "row has no viewState");
continue;
}
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
@@ -3151,6 +3174,7 @@
+ " onBottom=" + onBottom
+ " row=" + row.getEntry().getKey());
}
+ logHunAnimationEventAdded(key, type);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -4699,6 +4723,8 @@
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
+ logHunAnimationSkipped(row.getEntry().getKey(),
+ "previous hun appear animation cancelled");
return;
}
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 51ce779..0d0e5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -185,6 +185,7 @@
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackStateLogger;
+ private final NotificationStackScrollLogger mLogger;
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
@@ -662,8 +663,10 @@
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
- StackStateLogger stackLogger) {
+ StackStateLogger stackLogger,
+ NotificationStackScrollLogger logger) {
mStackStateLogger = stackLogger;
+ mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
mVisibilityProvider = visibilityProvider;
@@ -717,6 +720,7 @@
mView = view;
mView.setLogger(mStackStateLogger);
mView.setController(this);
+ mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
mView.setStatusBar(mStatusBar);
mView.setClearAllAnimationListener(this::onAnimationEnd);
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
new file mode 100644
index 0000000..04bf621
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.*
+import javax.inject.Inject
+
+class NotificationStackScrollLogger @Inject constructor(
+ @NotificationHeadsUpLog private val buffer: LogBuffer
+) {
+ fun hunAnimationSkipped(key: String, reason: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = reason
+ }, {
+ "heads up animation skipped: key: $str1 reason: $str2"
+ })
+ }
+ fun hunAnimationEventAdded(key: String, type: Int) {
+ val reason: String
+ reason = if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
+ "HEADS_UP_DISAPPEAR"
+ } else if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
+ "HEADS_UP_DISAPPEAR_CLICK"
+ } else if (type == ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ "HEADS_UP_APPEAR"
+ } else if (type == ANIMATION_TYPE_HEADS_UP_OTHER) {
+ "HEADS_UP_OTHER"
+ } else if (type == ANIMATION_TYPE_ADD) {
+ "ADD"
+ } else {
+ type.toString()
+ }
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = reason
+ }, {
+ "heads up animation added: $str1 with type $str2"
+ })
+ }
+
+ fun hunSkippedForUnexpectedState(key: String, expected: Boolean, actual: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ bool1 = expected
+ bool2 = actual
+ }, {
+ "HUN animation skipped for unexpected hun state: " +
+ "key: $str1 expected: $bool1 actual: $bool2"
+ })
+ }
+}
+
+private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 5f222af..b4e07f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -72,7 +72,10 @@
LOCKSCREEN_NOTIFICATION_FALSE_TOUCH(548),
@UiEvent(doc = "Expand the notification panel while unlocked")
- LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549);
+ LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549),
+
+ @UiEvent(doc = "Lockscreen > Tap on switch user icon")
+ LOCKSCREEN_SWITCH_USER_TAP(934);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 34009f5..cfa2456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -181,6 +181,7 @@
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
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;
@@ -204,6 +205,7 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -225,7 +227,8 @@
import javax.inject.Provider;
@StatusBarComponent.StatusBarScope
-public class NotificationPanelViewController extends PanelViewController {
+public class NotificationPanelViewController extends PanelViewController
+ implements NotifPanelEventSource {
private static final boolean DEBUG = false;
@@ -667,6 +670,8 @@
private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+ private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
+
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -2443,9 +2448,15 @@
private void updateQsExpansion() {
if (mQs == null) return;
- final float squishiness =
- mQsExpandImmediate || mQsExpanded ? 1f : mNotificationStackScrollLayoutController
- .getNotificationSquishinessFraction();
+ final float squishiness;
+ if (mQsExpandImmediate || mQsExpanded) {
+ squishiness = 1;
+ } else if (mLockscreenShadeTransitionController.getQSDragProgress() > 0) {
+ squishiness = mLockscreenShadeTransitionController.getQSDragProgress();
+ } else {
+ squishiness = mNotificationStackScrollLayoutController
+ .getNotificationSquishinessFraction();
+ }
final float qsExpansionFraction = computeQsExpansionFraction();
final float adjustedExpansionFraction = mShouldUseSplitNotificationShade
? 1f : computeQsExpansionFraction();
@@ -3429,6 +3440,28 @@
return mIsLaunchTransitionRunning;
}
+ @Override
+ public void setIsLaunchAnimationRunning(boolean running) {
+ boolean wasRunning = isLaunchTransitionRunning();
+ super.setIsLaunchAnimationRunning(running);
+ if (wasRunning != isLaunchTransitionRunning()) {
+ for (Callbacks cb : mNotifEventSourceCallbacks) {
+ cb.onLaunchingActivityChanged(running);
+ }
+ }
+ }
+
+ @Override
+ protected void setIsClosing(boolean isClosing) {
+ boolean wasClosing = isClosing();
+ super.setIsClosing(isClosing);
+ if (wasClosing != isClosing) {
+ for (Callbacks cb : mNotifEventSourceCallbacks) {
+ cb.onPanelCollapsingChanged(isClosing);
+ }
+ }
+ }
+
public void setLaunchTransitionEndRunnable(Runnable r) {
mLaunchAnimationEndRunnable = r;
}
@@ -4327,6 +4360,16 @@
.commitUpdate(mDisplayId);
}
+ @Override
+ public void registerCallbacks(Callbacks callbacks) {
+ mNotifEventSourceCallbacks.addIfAbsent(callbacks);
+ }
+
+ @Override
+ public void unregisterCallbacks(Callbacks callbacks) {
+ mNotifEventSourceCallbacks.remove(callbacks);
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 54d0b03..dc6efba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -506,7 +506,7 @@
private void endClosing() {
if (mClosing) {
- mClosing = false;
+ setIsClosing(false);
onClosingFinished();
}
}
@@ -581,7 +581,7 @@
boolean expandBecauseOfFalsing) {
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
- mClosing = true;
+ setIsClosing(true);
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
@@ -870,7 +870,7 @@
notifyExpandingStarted();
// Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- mClosing = true;
+ setIsClosing(true);
if (delayed) {
mNextCollapseSpeedUpFactor = speedUpFactor;
mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1151,6 +1151,14 @@
mIsLaunchAnimationRunning = running;
}
+ protected void setIsClosing(boolean isClosing) {
+ mClosing = isClosing;
+ }
+
+ protected boolean isClosing() {
+ return mClosing;
+ }
+
public void collapseWithDuration(int animationDuration) {
mFixedDuration = animationDuration;
collapse(false /* delayed */, 1.0f /* speedUpFactor */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index d19ed28..0059c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,12 +16,15 @@
package com.android.systemui.statusbar.phone;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY;
+
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.IActivityManager;
import android.app.SynchronousUserSwitchObserver;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -132,6 +135,7 @@
private final UserInfoController mUserInfoController;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
+ private final DevicePolicyManager mDevicePolicyManager;
private final StatusBarIconController mIconController;
private final CommandQueue mCommandQueue;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -172,7 +176,7 @@
LocationController locationController,
SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
AlarmManager alarmManager, UserManager userManager,
- RecordingController recordingController,
+ DevicePolicyManager devicePolicyManager, RecordingController recordingController,
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
@@ -190,6 +194,7 @@
mUserInfoController = userInfoController;
mIActivityManager = iActivityManager;
mUserManager = userManager;
+ mDevicePolicyManager = devicePolicyManager;
mRotationLockController = rotationLockController;
mDataSaver = dataSaverController;
mZenController = zenModeController;
@@ -288,7 +293,7 @@
// managed profile
mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status,
- mResources.getString(R.string.accessibility_managed_profile));
+ getManagedProfileAccessibilityString());
mIconController.setIconVisibility(mSlotManagedProfile, mManagedProfileIconVisible);
// data saver
@@ -343,6 +348,12 @@
mCommandQueue.addCallback(this);
}
+ private String getManagedProfileAccessibilityString() {
+ return mDevicePolicyManager.getString(
+ STATUS_BAR_WORK_ICON_ACCESSIBILITY,
+ () -> mResources.getString(R.string.accessibility_managed_profile));
+ }
+
@Override
public void onZenChanged(int zen) {
updateVolumeZen();
@@ -525,7 +536,7 @@
showIcon = true;
mIconController.setIcon(mSlotManagedProfile,
R.drawable.stat_sys_managed_profile_status,
- mResources.getString(R.string.accessibility_managed_profile));
+ getManagedProfileAccessibilityString());
} else {
showIcon = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 80ae070..c09c3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1544,6 +1544,12 @@
}
private void inflateStatusBarWindow() {
+ if (mStatusBarComponent != null) {
+ // Tear down
+ for (StatusBarComponent.Startable startable : mStatusBarComponent.getStartables()) {
+ startable.stop();
+ }
+ }
mStatusBarComponent = mStatusBarComponentFactory.create();
mFragmentService.addFragmentInstantiationProvider(mStatusBarComponent);
@@ -1572,6 +1578,11 @@
mCommandQueueCallbacks = mStatusBarComponent.getStatusBarCommandQueueCallbacks();
// Connect in to the status bar manager service
mCommandQueue.addCallback(mCommandQueueCallbacks);
+
+ // Perform all other initialization for StatusBarScope
+ for (StatusBarComponent.Startable startable : mStatusBarComponent.getStartables()) {
+ startable.start();
+ }
}
protected void startKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 61dba92..ad8e79e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -24,6 +24,7 @@
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.notification.collection.render.StatusBarNotifPanelEventSourceModule;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
@@ -35,6 +36,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
+import java.util.Set;
import javax.inject.Named;
import javax.inject.Scope;
@@ -50,7 +52,10 @@
* that it has many getter methods indicates that we need to access many of these classes from
* outside the component. Should more items be moved *into* this component to avoid so many getters?
*/
-@Subcomponent(modules = {StatusBarViewModule.class})
+@Subcomponent(modules = {
+ StatusBarNotifPanelEventSourceModule.class,
+ StatusBarViewModule.class
+})
@StatusBarComponent.StatusBarScope
public interface StatusBarComponent {
/**
@@ -70,8 +75,7 @@
@interface StatusBarScope {}
/**
- * Creates a {@link NotificationShadeWindowView}/
- * @return
+ * Creates a {@link NotificationShadeWindowView}.
*/
@StatusBarScope
NotificationShadeWindowView getNotificationShadeWindowView();
@@ -138,4 +142,18 @@
*/
@StatusBarScope
StatusBarInitializer getStatusBarInitializer();
+
+ /**
+ * Set of startables to be run after a StatusBarComponent has been constructed.
+ */
+ @StatusBarScope
+ Set<Startable> getStartables();
+
+ /**
+ * Performs initialization logic after {@link StatusBarComponent} has been constructed.
+ */
+ interface Startable {
+ void start();
+ void stop();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 33f2140..d903639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -23,6 +23,7 @@
import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 76615af0..b591545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
@@ -49,6 +50,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
@@ -82,6 +84,7 @@
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private final FeatureFlags mFeatureFlags;
private final UserSwitchDialogController mUserSwitchDialogController;
+ private final UiEventLogger mUiEventLogger;
private NotificationPanelViewController mNotificationPanelViewController;
private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
@@ -133,7 +136,8 @@
Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
ScreenOffAnimationController screenOffAnimationController,
FeatureFlags featureFlags,
- UserSwitchDialogController userSwitchDialogController) {
+ UserSwitchDialogController userSwitchDialogController,
+ UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
@@ -151,6 +155,7 @@
mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
mFeatureFlags = featureFlags;
mUserSwitchDialogController = userSwitchDialogController;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -173,7 +178,10 @@
return;
}
- // Tapping anywhere in the view will open QS user panel
+ // Tapping anywhere in the view will open the user switcher
+ mUiEventLogger.log(
+ LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
+
if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
mUserSwitchDialogController.showDialog(mView);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt
index d44d365..6d3345d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt
@@ -108,7 +108,7 @@
override fun stopFgs(userId: Int, packageName: String) {
init()
try {
- activityManager.makeServicesNonForeground(packageName, userId)
+ activityManager.stopAppForUser(packageName, userId)
} catch (e: RemoteException) {
e.rethrowFromSystemServer()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 60938fb..c326835 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -21,6 +21,7 @@
import android.os.UserManager;
import com.android.internal.R;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
@@ -36,7 +37,6 @@
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
-import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c6df1c1..72d72c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -45,6 +45,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.IdRes;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Insets;
@@ -63,9 +64,15 @@
import android.view.WindowManager;
import android.view.WindowMetrics;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.decor.CornerDecorProvider;
+import com.android.systemui.decor.DecorProvider;
+import com.android.systemui.decor.OverlayWindow;
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.tuner.TunerService;
@@ -81,6 +88,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Collections;
@RunWithLooper
@@ -96,6 +104,7 @@
private SecureSettings mSecureSettings;
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mThreadFactory;
+ private ArrayList<DecorProvider> mDecorProviders;
@Mock
private TunerService mTunerService;
@Mock
@@ -106,6 +115,16 @@
private PrivacyDotViewController mDotViewController;
@Mock
private TypedArray mMockTypedArray;
+ @Mock
+ private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory;
+ @Mock
+ private CornerDecorProvider mPrivacyDotTopLeftDecorProvider;
+ @Mock
+ private CornerDecorProvider mPrivacyDotTopRightDecorProvider;
+ @Mock
+ private CornerDecorProvider mPrivacyDotBottomLeftDecorProvider;
+ @Mock
+ private CornerDecorProvider mPrivacyDotBottomRightDecorProvider;
@Before
public void setup() {
@@ -129,9 +148,33 @@
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
when(mMockTypedArray.length()).thenReturn(0);
+ mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
+ R.id.privacy_dot_top_left_container,
+ DisplayCutout.BOUNDS_POSITION_TOP,
+ DisplayCutout.BOUNDS_POSITION_LEFT,
+ R.layout.privacy_dot_top_left));
+
+ mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
+ R.id.privacy_dot_top_right_container,
+ DisplayCutout.BOUNDS_POSITION_TOP,
+ DisplayCutout.BOUNDS_POSITION_RIGHT,
+ R.layout.privacy_dot_top_right));
+
+ mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
+ R.id.privacy_dot_bottom_left_container,
+ DisplayCutout.BOUNDS_POSITION_BOTTOM,
+ DisplayCutout.BOUNDS_POSITION_LEFT,
+ R.layout.privacy_dot_bottom_left));
+
+ mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
+ R.id.privacy_dot_bottom_right_container,
+ DisplayCutout.BOUNDS_POSITION_BOTTOM,
+ DisplayCutout.BOUNDS_POSITION_RIGHT,
+ R.layout.privacy_dot_bottom_right));
+
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
- mThreadFactory) {
+ mThreadFactory, mPrivacyDotDecorProviderFactory) {
@Override
public void start() {
super.start();
@@ -157,7 +200,7 @@
private void verifyRoundedCornerViewsVisibility(
@DisplayCutout.BoundsPosition final int overlayId,
@View.Visibility final int visibility) {
- final View overlay = mScreenDecorations.mOverlays[overlayId];
+ final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView();
final View left = overlay.findViewById(R.id.left);
final View right = overlay.findViewById(R.id.right);
assertNotNull(left);
@@ -166,23 +209,42 @@
assertThat(right.getVisibility()).isEqualTo(visibility);
}
+ @Nullable
+ private View findViewFromOverlays(@IdRes int id) {
+ for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
+ if (overlay == null) {
+ continue;
+ }
+
+ View view = overlay.getRootView().findViewById(id);
+ if (view != null) {
+ return view;
+ }
+ }
+ return null;
+ }
+
private void verifyTopDotViewsNullable(final boolean isAssertNull) {
+ View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
+ View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
if (isAssertNull) {
- assertNull(mScreenDecorations.mTopLeftDot);
- assertNull(mScreenDecorations.mTopRightDot);
+ assertNull(tl);
+ assertNull(tr);
} else {
- assertNotNull(mScreenDecorations.mTopLeftDot);
- assertNotNull(mScreenDecorations.mTopRightDot);
+ assertNotNull(tl);
+ assertNotNull(tr);
}
}
private void verifyBottomDotViewsNullable(final boolean isAssertNull) {
+ View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
+ View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
if (isAssertNull) {
- assertNull(mScreenDecorations.mBottomLeftDot);
- assertNull(mScreenDecorations.mBottomRightDot);
+ assertNull(bl);
+ assertNull(br);
} else {
- assertNotNull(mScreenDecorations.mBottomLeftDot);
- assertNotNull(mScreenDecorations.mBottomRightDot);
+ assertNotNull(bl);
+ assertNotNull(br);
}
}
@@ -193,14 +255,18 @@
private void verifyTopDotViewsVisibility(@View.Visibility final int visibility) {
verifyTopDotViewsNullable(false);
- assertThat(mScreenDecorations.mTopLeftDot.getVisibility()).isEqualTo(visibility);
- assertThat(mScreenDecorations.mTopRightDot.getVisibility()).isEqualTo(visibility);
+ View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
+ View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
+ assertThat(tl.getVisibility()).isEqualTo(visibility);
+ assertThat(tr.getVisibility()).isEqualTo(visibility);
}
private void verifyBottomDotViewsVisibility(@View.Visibility final int visibility) {
verifyBottomDotViewsNullable(false);
- assertThat(mScreenDecorations.mBottomLeftDot.getVisibility()).isEqualTo(visibility);
- assertThat(mScreenDecorations.mBottomRightDot.getVisibility()).isEqualTo(visibility);
+ View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
+ View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
+ assertThat(bl.getVisibility()).isEqualTo(visibility);
+ assertThat(br.getVisibility()).isEqualTo(visibility);
}
private void verifyDotViewsVisibility(@View.Visibility final int visibility) {
@@ -219,32 +285,32 @@
if (left) {
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
+ verify(mWindowManager, times(1)).addView(
+ eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()), any());
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
}
if (top) {
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
+ verify(mWindowManager, times(1)).addView(
+ eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()), any());
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
}
if (right) {
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any());
+ verify(mWindowManager, times(1)).addView(
+ eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()), any());
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
}
if (bottom) {
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+ verify(mWindowManager, times(1)).addView(
+ eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()), any());
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
}
@@ -381,18 +447,18 @@
doReturn(null).when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- View leftRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.left);
- View rightRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.right);
+ View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
+ .findViewById(R.id.left);
+ View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
+ .findViewById(R.id.right);
verify(mScreenDecorations, atLeastOnce())
.setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
verify(mScreenDecorations, atLeastOnce())
.setSize(rightRoundedCorner, new Point(testTopRadius, testTopRadius));
- leftRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.left);
- rightRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.right);
+ leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
+ .findViewById(R.id.left);
+ rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
+ .findViewById(R.id.right);
verify(mScreenDecorations, atLeastOnce())
.setSize(leftRoundedCorner, new Point(testBottomRadius, testBottomRadius));
verify(mScreenDecorations, atLeastOnce())
@@ -414,26 +480,26 @@
mScreenDecorations.start();
final Point topRadius = new Point(testTopRadius, testTopRadius);
final Point bottomRadius = new Point(testBottomRadius, testBottomRadius);
- View leftRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.left);
+ View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+ .findViewById(R.id.left);
boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left);
verify(mScreenDecorations, atLeastOnce())
.setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
- View rightRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.right);
+ View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+ .findViewById(R.id.right);
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
.setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
- leftRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.left);
+ leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+ .findViewById(R.id.left);
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left);
verify(mScreenDecorations, atLeastOnce())
.setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
- rightRoundedCorner =
- mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.right);
+ rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+ .findViewById(R.id.right);
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
.setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
@@ -790,6 +856,22 @@
mScreenDecorations.onConfigurationChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, true, false);
+
+ // Verify each privacy dot id appears only once
+ mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
+ int findCount = 0;
+ for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
+ if (overlay == null) {
+ continue;
+ }
+ final View view = overlay.getRootView().findViewById(viewId);
+ if (view != null) {
+ findCount++;
+ }
+ }
+ assertEquals(1, findCount);
+ });
+
}
@Test
@@ -985,8 +1067,16 @@
R.bool.config_roundedCornerMultipleRadius, multipleRadius);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
- mContext.getOrCreateTestableResources().addOverride(
- R.bool.config_enablePrivacyDot, privacyDot);
+
+ mDecorProviders = new ArrayList<>();
+ if (privacyDot) {
+ mDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
+ mDecorProviders.add(mPrivacyDotTopRightDecorProvider);
+ mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
+ mDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
+ }
+ when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders);
+ when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
}
private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 786f547..5d39eef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -628,6 +628,34 @@
verify(mDisplayManager).unregisterDisplayListener(any());
}
+ @Test
+ public void testOnBiometricPromptShownCallback() {
+ // GIVEN a callback is registered
+ AuthController.Callback callback = mock(AuthController.Callback.class);
+ mAuthController.addCallback(callback);
+
+ // WHEN dialog is shown
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+
+ // THEN callback should be received
+ verify(callback).onBiometricPromptShown();
+ }
+
+ @Test
+ public void testOnBiometricPromptDismissedCallback() {
+ // GIVEN a callback is registered
+ AuthController.Callback callback = mock(AuthController.Callback.class);
+ mAuthController.addCallback(callback);
+
+ // WHEN dialog is shown and then dismissed
+ showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+ null /* credentialAttestation */);
+
+ // THEN callback should be received
+ verify(callback).onBiometricPromptDismissed();
+ }
+
// Helpers
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
new file mode 100644
index 0000000..40f335d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics.BiometricDisplayListener.SensorType.SideFingerprint;
+import static com.android.systemui.biometrics.BiometricDisplayListener.SensorType.UnderDisplayFingerprint;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Surface.Rotation;
+
+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.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class BiometricDisplayListenerTest extends SysuiTestCase {
+
+ // Dependencies
+ @Mock private DisplayManager mDisplayManager;
+ @Mock private Display mDisplay;
+ @Mock private Function0<Unit> mOnChangedCallback;
+ @Mock private UnderDisplayFingerprint mUdfpsType;
+ @Mock private SideFingerprint mSidefpsType;
+ private Handler mHandler;
+ private Context mContextSpy;
+
+ // Captors
+ @Captor private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ // Set up mocks
+ mContextSpy = spy(mContext);
+ when(mContextSpy.getDisplay()).thenReturn(mDisplay);
+
+ // Create a real handler with a TestableLooper.
+ TestableLooper testableLooper = TestableLooper.get(this);
+ mHandler = new Handler(testableLooper.getLooper());
+ }
+
+ @Test
+ public void registersDisplayListener_whenEnabled() {
+ BiometricDisplayListener listener = new BiometricDisplayListener(
+ mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+ listener.enable();
+ verify(mDisplayManager).registerDisplayListener(any(), same(mHandler));
+ }
+
+ @Test
+ public void unregistersDisplayListener_whenDisabled() {
+ BiometricDisplayListener listener = new BiometricDisplayListener(
+ mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+ listener.enable();
+ listener.disable();
+ verify(mDisplayManager).unregisterDisplayListener(any());
+ }
+
+ @Test
+ public void detectsRotationChanges_forUdfps_relativeToRotationWhenEnabled() {
+ // Create a listener when the rotation is portrait.
+ when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+ BiometricDisplayListener listener = new BiometricDisplayListener(
+ mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+
+ // Rotate the device to landscape and then enable the listener.
+ when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_90);
+ listener.enable();
+ verify(mDisplayManager).registerDisplayListener(mDisplayListenerCaptor.capture(),
+ same(mHandler));
+
+ // Rotate the device back to portrait and ensure the rotation is detected.
+ when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+ mDisplayListenerCaptor.getValue().onDisplayChanged(999);
+ verify(mOnChangedCallback).invoke();
+ }
+
+ @Test
+ public void callsOnChanged_forUdfps_onlyWhenRotationChanges() {
+ final @Rotation int[] rotations =
+ new int[]{
+ Surface.ROTATION_0,
+ Surface.ROTATION_90,
+ Surface.ROTATION_180,
+ Surface.ROTATION_270
+ };
+
+ for (@Rotation int rot1 : rotations) {
+ for (@Rotation int rot2 : rotations) {
+ // Make the third rotation the same as the first one to simplify this test.
+ @Rotation int rot3 = rot1;
+
+ // Clean up prior interactions.
+ reset(mDisplayManager);
+ reset(mDisplay);
+ reset(mOnChangedCallback);
+
+ // Set up the mock for 3 invocations.
+ when(mDisplay.getRotation()).thenReturn(rot1, rot2, rot3);
+
+ BiometricDisplayListener listener = new BiometricDisplayListener(
+ mContextSpy, mDisplayManager, mHandler, mUdfpsType, mOnChangedCallback);
+ listener.enable();
+
+ // The listener should record the current rotation and register a display listener.
+ verify(mDisplay).getRotation();
+ verify(mDisplayManager)
+ .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler));
+
+ // Test the first rotation since the listener was enabled.
+ mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+ if (rot2 != rot1) {
+ verify(mOnChangedCallback).invoke();
+ } else {
+ verify(mOnChangedCallback, never()).invoke();
+ }
+
+ // Test continued rotations.
+ mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+ if (rot3 != rot2) {
+ verify(mOnChangedCallback, times(2)).invoke();
+ } else {
+ verify(mOnChangedCallback, never()).invoke();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void callsOnChanged_forSideFingerprint_whenAnythingDisplayChanges() {
+ // Any rotation will do for this test, we just need to return something.
+ when(mDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+
+ BiometricDisplayListener listener = new BiometricDisplayListener(
+ mContextSpy, mDisplayManager, mHandler, mSidefpsType, mOnChangedCallback);
+ listener.enable();
+
+ // The listener should register a display listener.
+ verify(mDisplayManager)
+ .registerDisplayListener(mDisplayListenerCaptor.capture(), same(mHandler));
+
+ // mOnChangedCallback should be invoked for all calls to onDisplayChanged.
+ mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+ mDisplayListenerCaptor.getValue().onDisplayChanged(123);
+ verify(mOnChangedCallback, times(2)).invoke();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
new file mode 100644
index 0000000..ca74df0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.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.decor
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.DisplayCutout
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class OverlayWindowTest : SysuiTestCase() {
+
+ companion object {
+ private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container
+ private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right
+ }
+
+ private lateinit var overlay: OverlayWindow
+
+ @Mock private lateinit var layoutInflater: LayoutInflater
+ @Mock private lateinit var decorProvider: DecorProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ layoutInflater = spy(LayoutInflater.from(mContext))
+
+ overlay = OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
+
+ whenever(decorProvider.viewId).thenReturn(TEST_DECOR_VIEW_ID)
+ whenever(decorProvider.inflateView(
+ eq(layoutInflater),
+ eq(overlay.rootView),
+ anyInt())
+ ).then {
+ val layoutInflater = it.getArgument<LayoutInflater>(0)
+ val parent = it.getArgument<ViewGroup>(1)
+ layoutInflater.inflate(TEST_DECOR_LAYOUT_ID, parent)
+ return@then parent.getChildAt(parent.childCount - 1)
+ }
+ }
+
+ @Test
+ fun testAnyBoundsPositionShallNoExceptionForConstructor() {
+ OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_LEFT)
+ OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_TOP)
+ OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
+ OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_BOTTOM)
+ }
+
+ @Test
+ fun testAddProvider() {
+ @Surface.Rotation val rotation = Surface.ROTATION_270
+ overlay.addDecorProvider(decorProvider, rotation)
+ verify(decorProvider, Mockito.times(1)).inflateView(
+ eq(layoutInflater), eq(overlay.rootView), eq(rotation))
+ val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
+ Assert.assertNotNull(viewFoundFromRootView)
+ Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID))
+ }
+
+ @Test
+ fun testRemoveView() {
+ @Surface.Rotation val rotation = Surface.ROTATION_270
+ overlay.addDecorProvider(decorProvider, rotation)
+ overlay.removeView(TEST_DECOR_VIEW_ID)
+ val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
+ Assert.assertNull(viewFoundFromRootView)
+ Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID))
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
new file mode 100644
index 0000000..bac0817
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.decor
+
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() {
+ private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory
+
+ @Mock private lateinit var resources: Resources
+
+ @Before
+ fun setUp() {
+ resources = spy(mContext.resources)
+ mPrivacyDotDecorProviderFactory = PrivacyDotDecorProviderFactory(resources)
+ }
+
+ private fun setPrivacyDotResources(isEnable: Boolean) {
+ whenever(resources.getBoolean(R.bool.config_enablePrivacyDot)).thenReturn(isEnable)
+ }
+
+ @Test
+ fun testGetNoCornerDecorProviderWithNoPrivacyDot() {
+ setPrivacyDotResources(false)
+
+ Assert.assertEquals(false, mPrivacyDotDecorProviderFactory.hasProviders)
+ Assert.assertEquals(0, mPrivacyDotDecorProviderFactory.providers.size)
+ }
+
+ @Test
+ fun testGet4CornerDecorProvidersWithPrivacyDot() {
+ setPrivacyDotResources(true)
+ val providers = mPrivacyDotDecorProviderFactory.providers
+
+ Assert.assertEquals(true, mPrivacyDotDecorProviderFactory.hasProviders)
+ Assert.assertEquals(4, providers.size)
+ Assert.assertEquals(1, providers.count {
+ ((it.viewId == R.id.privacy_dot_top_left_container)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+ })
+ Assert.assertEquals(1, providers.count {
+ ((it.viewId == R.id.privacy_dot_top_right_container)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+ })
+ Assert.assertEquals(1, providers.count {
+ ((it.viewId == R.id.privacy_dot_bottom_left_container)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+ })
+ Assert.assertEquals(1, providers.count {
+ ((it.viewId == R.id.privacy_dot_bottom_right_container)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+ and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+ })
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 6b156a4..b3b5fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -33,12 +33,12 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.SysuiTestableContext;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -65,10 +65,6 @@
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
- @Rule
- public SysuiTestableContext mContext = new SysuiTestableContext(
- InstrumentationRegistry.getContext(), mLeakCheck);
-
WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
@Mock
@@ -89,6 +85,13 @@
@Mock
DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ @Mock
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ @Mock
+ DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+
+
DreamOverlayService mService;
@Before
@@ -102,6 +105,8 @@
.thenReturn(mLifecycleOwner);
when(mDreamOverlayComponent.getLifecycleRegistry())
.thenReturn(mLifecycleRegistry);
+ when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
+ .thenReturn(mDreamOverlayTouchMonitor);
when(mDreamOverlayComponentFactory
.create(any(), any()))
.thenReturn(mDreamOverlayComponent);
@@ -109,7 +114,8 @@
.thenReturn(mDreamOverlayContainerView);
mService = new DreamOverlayService(mContext, mMainExecutor,
- mDreamOverlayComponentFactory);
+ mDreamOverlayComponentFactory,
+ mKeyguardUpdateMonitor);
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
index afc0309d..feeea5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -85,7 +85,9 @@
verify(observer).onChanged(collectionCaptor.capture());
- assertThat(collectionCaptor.getValue().equals(targetCollection)).isTrue();
+ final Collection collection = collectionCaptor.getValue();
+ assertThat(collection.containsAll(targetCollection)
+ && targetCollection.containsAll(collection)).isTrue();
Mockito.clearInvocations(observer);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index d080bbc..967b30d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -81,7 +81,7 @@
final boolean properDirection = (invalidPosition & position) != invalidPosition;
try {
- final ComplicationLayoutParams params = new ComplicationLayoutParams(
+ new ComplicationLayoutParams(
100,
100,
position,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
new file mode 100644
index 0000000..74b217b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dreams.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+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.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private static class Environment {
+ private final InputSessionComponent.Factory mInputFactory;
+ private final InputSession mInputSession;
+ private final Lifecycle mLifecycle;
+ private final LifecycleOwner mLifecycleOwner;
+ private final DreamOverlayTouchMonitor mMonitor;
+ private final DefaultLifecycleObserver mLifecycleObserver;
+ private final InputChannelCompat.InputEventListener mEventListener;
+ private final GestureDetector.OnGestureListener mGestureListener;
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ Environment(Set<DreamTouchHandler> handlers) {
+ mLifecycle = Mockito.mock(Lifecycle.class);
+ mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
+
+ mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
+ final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
+ mInputSession = Mockito.mock(InputSession.class);
+
+ when(mInputFactory.create(any(), any(), any(), anyBoolean()))
+ .thenReturn(inputComponent);
+ when(inputComponent.getInputSession()).thenReturn(mInputSession);
+
+ mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory, handlers);
+ mMonitor.init();
+
+ final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
+ ArgumentCaptor.forClass(LifecycleObserver.class);
+ verify(mLifecycle).addObserver(lifecycleObserverCaptor.capture());
+ assertThat(lifecycleObserverCaptor.getValue() instanceof DefaultLifecycleObserver)
+ .isTrue();
+ mLifecycleObserver = (DefaultLifecycleObserver) lifecycleObserverCaptor.getValue();
+
+ updateLifecycle(observer -> observer.first.onResume(observer.second));
+
+ // Capture creation request.
+ final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mInputFactory).create(any(), inputEventListenerCaptor.capture(),
+ gestureListenerCaptor.capture(),
+ eq(true));
+ mEventListener = inputEventListenerCaptor.getValue();
+ mGestureListener = gestureListenerCaptor.getValue();
+ }
+
+ void executeAll() {
+ mExecutor.runAllReady();
+ }
+
+ void publishInputEvent(InputEvent event) {
+ mEventListener.onInputEvent(event);
+ }
+
+ void publishGestureEvent(Consumer<GestureDetector.OnGestureListener> listenerConsumer) {
+ listenerConsumer.accept(mGestureListener);
+ }
+
+ void updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer) {
+ consumer.accept(Pair.create(mLifecycleObserver, mLifecycleOwner));
+ }
+
+ void verifyInputSessionDispose() {
+ verify(mInputSession).dispose();
+ Mockito.clearInvocations(mInputSession);
+ }
+ }
+
+ @Test
+ public void testInputEventPropagation() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(touchHandler);
+
+ // First event will be missed since we register after the execution loop,
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+ verify(eventListener).onInputEvent(eq(event));
+ }
+
+ @Test
+ public void testInputGesturePropagation() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final GestureDetector.OnGestureListener gestureListener =
+ registerGestureListener(touchHandler);
+
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ environment.publishGestureEvent(onGestureListener -> onGestureListener.onShowPress(event));
+ verify(gestureListener).onShowPress(eq(event));
+ }
+
+ @Test
+ public void testGestureConsumption() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final GestureDetector.OnGestureListener gestureListener =
+ registerGestureListener(touchHandler);
+
+ when(gestureListener.onDown(any())).thenReturn(true);
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ environment.publishGestureEvent(onGestureListener -> {
+ assertThat(onGestureListener.onDown(event)).isTrue();
+ });
+
+ verify(gestureListener).onDown(eq(event));
+ }
+
+ @Test
+ public void testBroadcast() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final HashSet<InputChannelCompat.InputEventListener> inputListeners = new HashSet<>();
+
+ inputListeners.add(registerInputEventListener(touchHandler));
+ inputListeners.add(registerInputEventListener(touchHandler));
+ inputListeners.add(registerInputEventListener(touchHandler2));
+
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ environment.publishInputEvent(event);
+
+ inputListeners
+ .stream()
+ .forEach(inputEventListener -> verify(inputEventListener).onInputEvent(event));
+ }
+
+ @Test
+ public void testPush() throws InterruptedException, ExecutionException {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(session);
+
+ final ListenableFuture<DreamTouchHandler.TouchSession> frontSessionFuture = session.push();
+ environment.executeAll();
+ final DreamTouchHandler.TouchSession frontSession = frontSessionFuture.get();
+ final InputChannelCompat.InputEventListener frontEventListener =
+ registerInputEventListener(frontSession);
+
+ final MotionEvent event = Mockito.mock(MotionEvent.class);
+ environment.publishInputEvent(event);
+
+ verify(frontEventListener).onInputEvent(eq(event));
+ verify(eventListener, never()).onInputEvent(any());
+
+ Mockito.clearInvocations(eventListener, frontEventListener);
+
+ ListenableFuture<DreamTouchHandler.TouchSession> sessionFuture = frontSession.pop();
+ environment.executeAll();
+
+ DreamTouchHandler.TouchSession returnedSession = sessionFuture.get();
+ assertThat(session == returnedSession).isTrue();
+
+ environment.executeAll();
+
+ final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
+ environment.publishInputEvent(followupEvent);
+
+ verify(eventListener).onInputEvent(eq(followupEvent));
+ verify(frontEventListener, never()).onInputEvent(any());
+ }
+
+ @Test
+ public void testPause() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(touchHandler);
+
+ // First event will be missed since we register after the execution loop,
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+ verify(eventListener).onInputEvent(eq(event));
+
+ environment.updateLifecycle(observerOwnerPair -> {
+ observerOwnerPair.first.onPause(observerOwnerPair.second);
+ });
+
+ environment.verifyInputSessionDispose();
+ }
+
+ @Test
+ public void testPilfering() {
+ final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
+ final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final DreamTouchHandler.TouchSession session1 = captureSession(touchHandler1);
+ final GestureDetector.OnGestureListener gestureListener1 =
+ registerGestureListener(session1);
+
+ final DreamTouchHandler.TouchSession session2 = captureSession(touchHandler2);
+ final GestureDetector.OnGestureListener gestureListener2 =
+ registerGestureListener(session2);
+ when(gestureListener2.onDown(any())).thenReturn(true);
+
+ final MotionEvent gestureEvent = Mockito.mock(MotionEvent.class);
+ environment.publishGestureEvent(
+ onGestureListener -> onGestureListener.onDown(gestureEvent));
+
+ Mockito.clearInvocations(gestureListener1, gestureListener2);
+
+ final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
+ environment.publishGestureEvent(
+ onGestureListener -> onGestureListener.onDown(followupEvent));
+
+ verify(gestureListener1, never()).onDown(any());
+ verify(gestureListener2).onDown(eq(followupEvent));
+ }
+
+ public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
+ final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
+ GestureDetector.OnGestureListener.class);
+ final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ verify(handler).onSessionStart(sessionCaptor.capture());
+ sessionCaptor.getValue().registerGestureListener(gestureListener);
+
+ return gestureListener;
+ }
+
+ public GestureDetector.OnGestureListener registerGestureListener(
+ DreamTouchHandler.TouchSession session) {
+ final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
+ GestureDetector.OnGestureListener.class);
+ session.registerGestureListener(gestureListener);
+
+ return gestureListener;
+ }
+
+ public InputChannelCompat.InputEventListener registerInputEventListener(
+ DreamTouchHandler.TouchSession session) {
+ final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
+ InputChannelCompat.InputEventListener.class);
+ session.registerInputListener(eventListener);
+
+ return eventListener;
+ }
+
+ public DreamTouchHandler.TouchSession captureSession(DreamTouchHandler handler) {
+ final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ verify(handler).onSessionStart(sessionCaptor.capture());
+ return sessionCaptor.getValue();
+ }
+
+ public InputChannelCompat.InputEventListener registerInputEventListener(
+ DreamTouchHandler handler) {
+ return registerInputEventListener(captureSession(handler));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
new file mode 100644
index 0000000..b8e9cf4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SessionTrackerTest extends SysuiTestCase {
+ @Mock
+ private IStatusBarService mStatusBarService;
+ @Mock
+ private AuthController mAuthController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+
+ @Captor
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+ KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
+ @Captor
+ ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCallbackCaptor;
+ KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor
+ ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ AuthController.Callback mAuthControllerCallback;
+
+ private SessionTracker mSessionTracker;
+
+ @Before
+ public void setup() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+
+ mSessionTracker = new SessionTracker(
+ mContext,
+ mStatusBarService,
+ mAuthController,
+ mKeyguardUpdateMonitor,
+ mKeyguardStateController
+ );
+ }
+
+ @Test
+ public void testOnStartShowingKeyguard() throws RemoteException {
+ // GIVEN the keyguard is showing before start
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // WHEN started
+ mSessionTracker.start();
+
+ // THEN keyguard session has a session id
+ assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+ // THEN send event to status bar service
+ verify(mStatusBarService).onSessionStarted(eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ @Test
+ public void testNoSessions() throws RemoteException {
+ // GIVEN no sessions
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ // WHEN started
+ mSessionTracker.start();
+
+ // THEN all sessions are null
+ for (int sessionType : ALL_SESSIONS) {
+ assertNull(mSessionTracker.getSessionId(sessionType));
+ }
+ }
+
+ @Test
+ public void testBiometricPromptShowing() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureAuthControllerCallback();
+
+ // WHEN auth controller shows the biometric prompt
+ mAuthControllerCallback.onBiometricPromptShown();
+
+ // THEN the biometric prompt session has a session id
+ assertNotNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
+
+ // THEN session started event gets sent to status bar service
+ verify(mStatusBarService).onSessionStarted(
+ eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
+ }
+
+ @Test
+ public void testBiometricPromptDismissed() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureAuthControllerCallback();
+
+ // WHEN auth controller shows the biometric prompt and then hides it
+ mAuthControllerCallback.onBiometricPromptShown();
+ mAuthControllerCallback.onBiometricPromptDismissed();
+
+ // THEN the biometric prompt session no longer has a session id
+ assertNull(mSessionTracker.getSessionId(SESSION_BIOMETRIC_PROMPT));
+
+ // THEN session end event gets sent to status bar service
+ verify(mStatusBarService).onSessionEnded(
+ eq(SESSION_BIOMETRIC_PROMPT), any(InstanceId.class));
+ }
+
+ @Test
+ public void testKeyguardSessionOnDeviceStartsSleeping() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+
+ // WHEN device starts going to sleep
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN the keyguard session has a session id
+ assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+ // THEN session start event gets sent to status bar service
+ verify(mStatusBarService).onSessionStarted(
+ eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ @Test
+ public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardStateControllerCallback();
+
+ // WHEN keyguard becomes visible (ie: from lockdown)
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+
+ // THEN the keyguard session has a session id
+ assertNotNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+ // THEN session start event gets sent to status bar service
+ verify(mStatusBarService).onSessionStarted(
+ eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ @Test
+ public void testKeyguardSessionOnKeyguardNotShowing() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardStateControllerCallback();
+
+ // WHEN keyguard was showing and now it's not
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+
+ // THEN the keyguard session no longer has a session id
+ assertNull(mSessionTracker.getSessionId(SESSION_KEYGUARD));
+
+ // THEN session end event gets sent to status bar service
+ verify(mStatusBarService).onSessionEnded(
+ eq(SESSION_KEYGUARD), any(InstanceId.class));
+ }
+
+ void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
+
+ void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateCallbackCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCallbackCaptor.getValue();
+ }
+
+ void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(
+ mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index cf1a36a..529f6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -207,6 +207,9 @@
.thenReturn(DEVICE_OWNER_COMPONENT);
when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+ when(mDevicePolicyManager.getString(anyString(), any())).thenReturn(mDisclosureGeneric);
+ when(mDevicePolicyManager.getString(anyString(), any(), anyString()))
+ .thenReturn(mDisclosureWithOrganization);
mWakeLock = new WakeLockFake();
mWakeLockBuilder = new WakeLockFake.Builder(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
index c7c8d04..6059afe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
@@ -246,7 +246,7 @@
public void testStopFgs() throws RemoteException {
String pkgName = "package.name";
mController.stopFgs(0, pkgName);
- verify(mActivityManager).makeServicesNonForeground(pkgName, 0);
+ verify(mActivityManager).stopAppForUser(pkgName, 0);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 25dd23a..2e84482 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -112,7 +112,7 @@
private CollectionReadyForBuildListener mReadyForBuildListener;
private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
private List<NotificationEntry> mEntrySet = new ArrayList<>();
- private List<ListEntry> mBuiltList;
+ private List<ListEntry> mBuiltList = new ArrayList<>();
private TestableStabilityManager mStabilityManager;
private TestableNotifFilter mFinalizeFilter;
@@ -1642,6 +1642,19 @@
}
@Test
+ public void testPipelineRunDisallowedDueToVisualStability() {
+ // GIVEN pipeline run not allowed due to visual stability
+ mStabilityManager.setAllowPipelineRun(false);
+
+ // WHEN we try to run the pipeline with a change
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN there is no change; the pipeline did not run
+ verifyBuiltList();
+ }
+
+ @Test
public void testIsSorted() {
Comparator<Integer> intCmp = Integer::compare;
assertTrue(ShadeListBuilder.isSorted(Collections.emptyList(), intCmp));
@@ -2037,6 +2050,7 @@
}
private static class TestableStabilityManager extends NotifStabilityManager {
+ boolean mAllowPipelineRun = true;
boolean mAllowGroupChanges = true;
boolean mAllowSectionChanges = true;
boolean mAllowEntryReodering = true;
@@ -2060,6 +2074,15 @@
return this;
}
+ TestableStabilityManager setAllowPipelineRun(boolean allowPipelineRun) {
+ mAllowPipelineRun = allowPipelineRun;
+ return this;
+ }
+
+ @Override
+ public boolean isPipelineRunAllowed() {
+ return mAllowPipelineRun;
+ }
@Override
public void onBeginRun() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 5df1d28..17b3b1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -65,9 +66,11 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotifPanelEventSource mNotifPanelEventSource;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
+ @Captor private ArgumentCaptor<NotifPanelEventSource.Callbacks> mNotifPanelEventsCallbackCaptor;
@Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -75,6 +78,7 @@
private WakefulnessLifecycle.Observer mWakefulnessObserver;
private StatusBarStateController.StateListener mStatusBarStateListener;
+ private NotifPanelEventSource.Callbacks mNotifPanelEventsCallback;
private NotifStabilityManager mNotifStabilityManager;
private NotificationEntry mEntry;
@@ -83,11 +87,12 @@
MockitoAnnotations.initMocks(this);
mCoordinator = new VisualStabilityCoordinator(
+ mFakeExecutor,
mDumpManager,
mHeadsUpManager,
- mWakefulnessLifecycle,
+ mNotifPanelEventSource,
mStatusBarStateController,
- mFakeExecutor);
+ mWakefulnessLifecycle);
mCoordinator.attach(mNotifPipeline);
@@ -98,6 +103,9 @@
verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
mStatusBarStateListener = mSBStateListenerCaptor.getValue();
+ verify(mNotifPanelEventSource).registerCallbacks(mNotifPanelEventsCallbackCaptor.capture());
+ mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
+
verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
@@ -319,6 +327,58 @@
}
@Test
+ public void testNotLaunchingActivityAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setActivityLaunching(true);
+
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false);
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNotCollapsingPanelAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setPanelCollapsing(true);
+
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false);
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setPanelCollapsing(true);
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false);
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNeverSuppressPipelineRunFromLaunchActivity_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setActivityLaunching(true);
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false);
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() {
// GIVEN visual stability is being maintained b/c panel is expanded
setPulsing(false);
@@ -370,6 +430,14 @@
}
+ private void setActivityLaunching(boolean activityLaunching) {
+ mNotifPanelEventsCallback.onLaunchingActivityChanged(activityLaunching);
+ }
+
+ private void setPanelCollapsing(boolean collapsing) {
+ mNotifPanelEventsCallback.onPanelCollapsingChanged(collapsing);
+ }
+
private void setPulsing(boolean pulsing) {
mStatusBarStateListener.onPulsingChanged(pulsing);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
new file mode 100644
index 0000000..7b10f5a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.interruption;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.core.os.CancellationSignal;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class HeadsUpViewBinderTest extends SysuiTestCase {
+ private HeadsUpViewBinder mViewBinder;
+ @Mock private NotificationMessagingUtil mNotificationMessagingUtil;
+ @Mock private RowContentBindStage mBindStage;
+ @Mock private HeadsUpViewBinderLogger mLogger;
+ @Mock private NotificationEntry mEntry;
+ @Mock private ExpandableNotificationRow mRow;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mViewBinder = new HeadsUpViewBinder(mNotificationMessagingUtil, mBindStage, mLogger);
+ when(mEntry.getKey()).thenReturn("key");
+ when(mEntry.getRow()).thenReturn(mRow);
+ when(mBindStage.getStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
+ }
+
+ @Test
+ public void testLoggingWorks() {
+ AtomicReference<NotifBindPipeline.BindCallback> callback = new AtomicReference<>();
+ when(mBindStage.requestRebind(any(), any())).then(i -> {
+ callback.set(i.getArgument(1));
+ return new CancellationSignal();
+ });
+
+ mViewBinder.bindHeadsUpView(mEntry, null);
+ verify(mLogger, times(1)).startBindingHun(eq("key"));
+ verify(mLogger, times(0)).entryBoundSuccessfully(eq("key"));
+ verify(mLogger, times(0)).currentOngoingBindingAborted(eq("key"));
+
+ callback.get().onBindFinished(mEntry);
+
+ verify(mLogger, times(1)).entryBoundSuccessfully(eq("key"));
+ mViewBinder.bindHeadsUpView(mEntry, null);
+
+ callback.get().onBindFinished(mEntry);
+
+ verify(mLogger, times(2)).startBindingHun(eq("key"));
+ verify(mLogger, times(2)).entryBoundSuccessfully(eq("key"));
+ verify(mLogger, times(1)).currentOngoingBindingAborted(eq("key"));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 86a705f..c4f954f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -138,6 +138,7 @@
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
+ @Mock private NotificationStackScrollLogger mLogger;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -193,7 +194,8 @@
mVisualStabilityManager,
mShadeController,
mJankMonitor,
- mStackLogger
+ mStackLogger,
+ mLogger
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f4f5bfa..b7c00fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -367,6 +367,10 @@
when(mStatusBarComponentFactory.create()).thenReturn(mStatusBarComponent);
when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn(
mNotificationShadeWindowViewController);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
mShadeController = new ShadeControllerImpl(mCommandQueue,
mStatusBarStateController, mNotificationShadeWindowController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 6364d2f..48b1732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -36,6 +36,7 @@
import com.android.internal.R;
import com.android.internal.view.RotationPolicy;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
new file mode 100644
index 0000000..a57f6a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.CommunalStateController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {
+ @Mock
+ private lateinit var screenLifecycle: ScreenLifecycle
+
+ @Mock
+ private lateinit var userSwitcherController: UserSwitcherController
+
+ @Mock
+ private lateinit var communalStateController: CommunalStateController
+
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+
+ @Mock
+ private lateinit var configurationController: ConfigurationController
+
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
+
+ @Mock
+ private lateinit var dozeParameters: DozeParameters
+
+ @Mock
+ private lateinit var userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>
+
+ @Mock
+ private lateinit var screenOffAnimationController: ScreenOffAnimationController
+
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+
+ @Mock
+ private lateinit var userSwitchDialogController: UserSwitchDialogController
+
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+
+ @Mock
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
+
+ private lateinit var view: FrameLayout
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ view = LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
+
+ keyguardQsUserSwitchController = KeyguardQsUserSwitchController(
+ view,
+ context,
+ context.resources,
+ screenLifecycle,
+ userSwitcherController,
+ communalStateController,
+ keyguardStateController,
+ falsingManager,
+ configurationController,
+ statusBarStateController,
+ dozeParameters,
+ userDetailViewAdapterProvider,
+ screenOffAnimationController,
+ featureFlags,
+ userSwitchDialogController,
+ uiEventLogger)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+ keyguardQsUserSwitchController
+ .setNotificationPanelViewController(notificationPanelViewController)
+ `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
+ `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
+ keyguardQsUserSwitchController.init()
+ }
+
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun testUiEventLogged() {
+ view.findViewById<View>(R.id.kg_multi_user_avatar)?.performClick()
+ verify(uiEventLogger, times(1))
+ .log(LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP)
+ }
+}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 196c6aa..e89dda9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -362,5 +362,11 @@
// Notify the user that some accessibility service has view and control permissions.
// package: android
NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005;
+
+ // Notify the user an abusive background app has been detected.
+ // Package: android
+ // Note: this is a base ID, multiple notifications will be posted for each
+ // abusive apps, with notification ID based off this ID.
+ NOTE_ABUSIVE_BG_APPS_BASE = 0xc1b2508; // 203105544
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 26760aa..af70692 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -79,6 +79,7 @@
":services.backup-sources",
":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex/service-bluetooth jar is ready
":backuplib-sources",
+ ":services.cloudsearch-sources",
":services.companion-sources",
":services.contentcapture-sources",
":services.contentsuggestions-sources",
@@ -133,6 +134,7 @@
"services.appwidget",
"services.autofill",
"services.backup",
+ "services.cloudsearch",
"services.companion",
"services.contentcapture",
"services.contentsuggestions",
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 27ea3d6..8fbdd81 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -32,6 +32,7 @@
import android.service.autofill.Dataset.DatasetFieldFilter;
import android.service.autofill.FillResponse;
import android.text.TextUtils;
+import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -63,7 +64,9 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -898,8 +901,11 @@
if (count <= 0) {
text = mContext.getString(R.string.autofill_picker_no_suggestions);
} else {
- text = mContext.getResources().getQuantityString(
- R.plurals.autofill_picker_some_suggestions, count, count);
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", count);
+ text = PluralsMessageFormatter.format(mContext.getResources(),
+ arguments,
+ R.string.autofill_picker_some_suggestions);
}
mListView.announceForAccessibility(text);
}
diff --git a/services/cloudsearch/Android.bp b/services/cloudsearch/Android.bp
new file mode 100644
index 0000000..e38e615
--- /dev/null
+++ b/services/cloudsearch/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.cloudsearch-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.cloudsearch",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.cloudsearch-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
new file mode 100644
index 0000000..dafe7a4
--- /dev/null
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.cloudsearch;
+
+import static android.Manifest.permission.MANAGE_CLOUDSEARCH;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.CLOUDSEARCH_SERVICE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.cloudsearch.ICloudSearchManager;
+import android.app.cloudsearch.ICloudSearchManagerCallback;
+import android.app.cloudsearch.SearchRequest;
+import android.app.cloudsearch.SearchResponse;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return cloudsearch targets given a query.
+ */
+public class CloudSearchManagerService extends
+ AbstractMasterSystemService<CloudSearchManagerService, CloudSearchPerUserService> {
+
+ private static final String TAG = CloudSearchManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public CloudSearchManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ R.string.config_defaultCloudSearchService), null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected CloudSearchPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new CloudSearchPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(CLOUDSEARCH_SERVICE, new CloudSearchManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_CLOUDSEARCH, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final CloudSearchPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class CloudSearchManagerStub extends ICloudSearchManager.Stub {
+
+ @Override
+ public void search(@NonNull SearchRequest searchRequest,
+ @NonNull ICloudSearchManagerCallback callBack) {
+ runForUserLocked("search", searchRequest.getRequestId(), (service) ->
+ service.onSearchLocked(searchRequest, callBack));
+ }
+
+ @Override
+ public void returnResults(IBinder token, String requestId, SearchResponse response) {
+ runForUserLocked("returnResults", requestId, (service) ->
+ service.onReturnResultsLocked(token, requestId, response));
+ }
+
+ public void destroy(@NonNull SearchRequest searchRequest) {
+ runForUserLocked("destroyCloudSearchSession", searchRequest.getRequestId(),
+ (service) -> service.onDestroyLocked(searchRequest.getRequestId()));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new CloudSearchManagerServiceShellCommand(CloudSearchManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final String requestId,
+ @NonNull final Consumer<CloudSearchPerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
+ null, null);
+
+ if (DEBUG) {
+ Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+ Context ctx = getContext();
+ if (!(ctx.checkCallingPermission(MANAGE_CLOUDSEARCH) == PERMISSION_GRANTED
+ || mServiceNameResolver.isTemporary(userId)
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final CloudSearchPerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java
new file mode 100644
index 0000000..51f5fd9
--- /dev/null
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.cloudsearch;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the CloudSearchManagerService.
+ */
+public class CloudSearchManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ CloudSearchManagerServiceShellCommand.class.getSimpleName();
+
+ private final CloudSearchManagerService mService;
+
+ public CloudSearchManagerServiceShellCommand(@NonNull CloudSearchManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ pw.println("CloudSearchService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("CloudSearchService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("CloudSearchManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
new file mode 100644
index 0000000..32d66af
--- /dev/null
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.cloudsearch;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.cloudsearch.ICloudSearchManagerCallback;
+import android.app.cloudsearch.SearchRequest;
+import android.app.cloudsearch.SearchResponse;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.cloudsearch.CloudSearchService;
+import android.service.cloudsearch.ICloudSearchService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.CircularQueue;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link CloudSearchManagerService}.
+ */
+public class CloudSearchPerUserService extends
+ AbstractPerUserSystemService<CloudSearchPerUserService, CloudSearchManagerService>
+ implements RemoteCloudSearchService.RemoteCloudSearchServiceCallbacks {
+
+ private static final String TAG = CloudSearchPerUserService.class.getSimpleName();
+ private static final int QUEUE_SIZE = 10;
+ @GuardedBy("mLock")
+ private final CircularQueue<String, CloudSearchCallbackInfo> mCallbackQueue =
+ new CircularQueue<>(QUEUE_SIZE);
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteCloudSearchService mRemoteService;
+ /**
+ * When {@code true}, remote service died but service state is kept so it's restored after
+ * the system re-binds to it.
+ */
+ @GuardedBy("mLock")
+ private boolean mZombie;
+
+ protected CloudSearchPerUserService(CloudSearchManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/177858728): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (isEnabledLocked()) {
+ // Send the pending sessions over to the service
+ resurrectSessionsLocked();
+ } else {
+ // Clear the remote service for the next call
+ updateRemoteServiceLocked();
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new cloudsearch session.
+ */
+ @GuardedBy("mLock")
+ public void onSearchLocked(@NonNull SearchRequest searchRequest,
+ @NonNull ICloudSearchManagerCallback callback) {
+ String filterList = searchRequest.getSearchConstraints().containsKey(
+ SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER)
+ ? searchRequest.getSearchConstraints().getString(
+ SearchRequest.CONSTRAINT_SEARCH_PROVIDER_FILTER) : "";
+
+ String remoteServicePackageName = getServiceComponentName().getPackageName();
+ // By default, all providers are marked as wanted.
+ boolean wantedProvider = true;
+ if (filterList.length() > 0) {
+ // If providers are specified by the client,
+ wantedProvider = false;
+ String[] providersSpecified = filterList.split(";");
+ for (int i = 0; i < providersSpecified.length; i++) {
+ if (providersSpecified[i].equals(remoteServicePackageName)) {
+ wantedProvider = true;
+ break;
+ }
+ }
+ }
+ // If the provider was not requested by the Client, the request will not be sent to the
+ // provider.
+ if (!wantedProvider) {
+ // TODO(216520546) Send a failure callback to the client.
+ return;
+ }
+ final boolean serviceExists = resolveService(searchRequest,
+ s -> s.onSearch(searchRequest));
+ String requestId = searchRequest.getRequestId();
+ if (serviceExists && !mCallbackQueue.containsKey(requestId)) {
+ final CloudSearchCallbackInfo sessionInfo = new CloudSearchCallbackInfo(
+ requestId, searchRequest, callback, callback.asBinder(), () -> {
+ synchronized (mLock) {
+ onDestroyLocked(requestId);
+ }
+ });
+ if (sessionInfo.linkToDeath()) {
+ mCallbackQueue.put(requestId, sessionInfo);
+ } else {
+ // destroy the session if calling process is already dead
+ onDestroyLocked(requestId);
+ }
+ }
+ }
+
+ /**
+ * Used to return results back to the clients.
+ */
+ public void onReturnResultsLocked(@NonNull IBinder token,
+ @NonNull String requestId,
+ @NonNull SearchResponse response) {
+ if (mCallbackQueue.containsKey(requestId)) {
+ response.setSource(mRemoteService.getComponentName().getPackageName());
+ final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.getElement(requestId);
+ try {
+ if (response.getStatusCode() == SearchResponse.SEARCH_STATUS_OK) {
+ sessionInfo.mCallback.onSearchSucceeded(response);
+ } else {
+ sessionInfo.mCallback.onSearchFailed(response);
+ }
+ } catch (RemoteException e) {
+ onDestroyLocked(requestId);
+ }
+ }
+ }
+
+ /**
+ * Notifies the server about the end of an existing cloudsearch session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyLocked(@NonNull String requestId) {
+ if (isDebug()) {
+ Slog.d(TAG, "onDestroyLocked(): requestId=" + requestId);
+ }
+ final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.removeElement(requestId);
+ sessionInfo.destroy();
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+ // Do nothing, we are just proxying to the cloudsearch service
+ }
+
+ @Override
+ public void onConnectedStateChanged(boolean connected) {
+ if (isDebug()) {
+ Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+ }
+ if (connected) {
+ synchronized (mLock) {
+ if (mZombie) {
+ // Validation check - shouldn't happen
+ if (mRemoteService == null) {
+ Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+ return;
+ }
+ mZombie = false;
+ resurrectSessionsLocked();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDied(RemoteCloudSearchService service) {
+ if (isDebug()) {
+ Slog.w(TAG, "onServiceDied(): service=" + service);
+ }
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ updateRemoteServiceLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ }
+
+ void onPackageUpdatedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageUpdatedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ void onPackageRestartedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageRestartedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ private void destroyAndRebindRemoteService() {
+ if (mRemoteService == null) {
+ return;
+ }
+
+ if (isDebug()) {
+ Slog.d(TAG, "Destroying the old remote service.");
+ }
+ mRemoteService.destroy();
+ mRemoteService = null;
+
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ mRemoteService = getRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ }
+
+ /**
+ * Called after the remote service connected, it's used to restore state from a 'zombie'
+ * service (i.e., after it died).
+ */
+ private void resurrectSessionsLocked() {
+ final int numCallbacks = mCallbackQueue.size();
+ if (isDebug()) {
+ Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+ + numCallbacks + " requests.");
+ }
+
+ for (CloudSearchCallbackInfo callbackInfo : mCallbackQueue.values()) {
+ callbackInfo.resurrectSessionLocked(this, callbackInfo.mToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ protected boolean resolveService(
+ @NonNull final SearchRequest requestId,
+ @NonNull final AbstractRemoteService.AsyncRequest<ICloudSearchService> cb) {
+
+ final RemoteCloudSearchService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.executeOnResolvedService(cb);
+ }
+ return service != null;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteCloudSearchService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteCloudSearchService(getContext(),
+ CloudSearchService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ private static final class CloudSearchCallbackInfo {
+ private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
+ final IBinder mToken;
+ @NonNull
+ final IBinder.DeathRecipient mDeathRecipient;
+ @NonNull
+ private final String mRequestId;
+ @NonNull
+ private final SearchRequest mSearchRequest;
+ private final ICloudSearchManagerCallback mCallback;
+
+ CloudSearchCallbackInfo(
+ @NonNull final String id,
+ @NonNull final SearchRequest request,
+ @NonNull final ICloudSearchManagerCallback callback,
+ @NonNull final IBinder token,
+ @NonNull final IBinder.DeathRecipient deathRecipient) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating CloudSearchSessionInfo for session Id=" + id);
+ }
+ mRequestId = id;
+ mSearchRequest = request;
+ mCallback = callback;
+ mToken = token;
+ mDeathRecipient = deathRecipient;
+ }
+
+ boolean linkToDeath() {
+ try {
+ mToken.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Caller is dead before session can be started, requestId: "
+ + mRequestId);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ void destroy() {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for Request Id=" + mRequestId);
+ }
+ if (mToken != null) {
+ mToken.unlinkToDeath(mDeathRecipient, 0);
+ }
+ mCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ }
+
+ void resurrectSessionLocked(CloudSearchPerUserService service, IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+ + ") for request Id=" + mRequestId);
+ }
+ service.onSearchLocked(mSearchRequest, mCallback);
+ }
+ }
+}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java b/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java
new file mode 100644
index 0000000..eb16d3b
--- /dev/null
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/RemoteCloudSearchService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.cloudsearch;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.cloudsearch.ICloudSearchService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.cloudsearch.CloudSearchService} implementation in another
+ * process.
+ */
+public class RemoteCloudSearchService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteCloudSearchService,
+ ICloudSearchService> {
+
+ private static final String TAG = "RemoteCloudSearchService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteCloudSearchServiceCallbacks mCallback;
+
+ public RemoteCloudSearchService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteCloudSearchServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected ICloudSearchService getServiceInterface(IBinder service) {
+ return ICloudSearchService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Schedules a request to bind to the remote service.
+ */
+ public void reconnect() {
+ super.scheduleBind();
+ }
+
+ /**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(@NonNull AsyncRequest<ICloudSearchService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteCloudSearchServiceCallbacks
+ extends VultureCallback<RemoteCloudSearchService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+
+ /**
+ * Notifies change in connected state of the remote service.
+ */
+ void onConnectedStateChanged(boolean connected);
+ }
+
+ @Override // from AbstractRemoteService
+ protected void handleOnConnectedStateChanged(boolean connected) {
+ if (mCallback != null) {
+ mCallback.onConnectedStateChanged(connected);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index b981ff1..7ebe33e 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -22,6 +22,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Binder.getCallingPid;
@@ -62,6 +63,7 @@
Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
+ map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER);
DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
}
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index e996eb4..d49cc11 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -20,6 +20,7 @@
import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import java.util.Collection;
+import java.util.List;
/**
* Battery stats local system service interface. This is used to pass internal data out of
@@ -42,6 +43,17 @@
public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
/**
+ * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+ * and per-UID basis.
+ *
+ * <p>
+ * Note: This is a slow running method and should be called from non-blocking threads only.
+ * </p>
+ */
+ public abstract List<BatteryUsageStats> getBatteryUsageStats(
+ List<BatteryUsageStatsQuery> queries);
+
+ /**
* Inform battery stats how many deferred jobs existed when the app got launched and how
* long ago was the last job execution for the app.
* @param uid the uid of the app.
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
new file mode 100644
index 0000000..db510cb
--- /dev/null
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+import android.os.SystemProperties;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.os.IBinaryTransparencyService;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+public class BinaryTransparencyService extends SystemService {
+ private static final String TAG = "TransparencyService";
+
+ private static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
+ private static final String VBMETA_DIGEST_UNAVAILABLE = "vbmeta-digest-unavailable";
+ private static final String SYSPROP_NAME_VBETA_DIGEST = "ro.boot.vbmeta.digest";
+
+ private static final String BINARY_HASH_ERROR = "SHA256HashError";
+
+ private final Context mContext;
+ private String mVbmetaDigest;
+ private HashMap<String, String> mBinaryHashes;
+ private HashMap<String, Long> mBinaryLastUpdateTimes;
+
+ final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
+
+ @Override
+ public String getSignedImageInfo() {
+ return mVbmetaDigest;
+ }
+
+ @Override
+ public Map getApexInfo() {
+ HashMap results = new HashMap();
+ if (!updateBinaryMeasurements()) {
+ Slog.e(TAG, "Error refreshing APEX measurements.");
+ return results;
+ }
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+ return results;
+ }
+
+ for (PackageInfo packageInfo : getInstalledApexs()) {
+ results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName));
+ }
+
+ return results;
+ }
+
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in,
+ @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args,
+ @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ (new ShellCommand() {
+
+ private int printSignedImageInfo() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean listAllPartitions = false;
+ String opt;
+
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-a":
+ listAllPartitions = true;
+ break;
+ default:
+ pw.println("ERROR: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String signedImageInfo = getSignedImageInfo();
+ pw.println("Image Info:");
+ pw.println(Build.FINGERPRINT);
+ pw.println(signedImageInfo);
+ pw.println("");
+
+ if (listAllPartitions) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ pw.println("ERROR: Failed to obtain an instance of package manager.");
+ return -1;
+ }
+
+ pw.println("Other partitions:");
+ List<Build.Partition> buildPartitions = Build.getFingerprintedPartitions();
+ for (Build.Partition buildPartition : buildPartitions) {
+ pw.println("Name: " + buildPartition.getName());
+ pw.println("Fingerprint: " + buildPartition.getFingerprint());
+ pw.println("Build time (ms): " + buildPartition.getBuildTimeMillis());
+ }
+ }
+ return 0;
+ }
+
+ private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
+ pw.println("--- Module Details ---");
+ pw.println("Module name: " + moduleInfo.getName());
+ pw.println("Module visibility: "
+ + (moduleInfo.isHidden() ? "hidden" : "visible"));
+ }
+
+ private int printAllApexs() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ String opt;
+
+ // refresh cache to make sure info is most up-to-date
+ if (!updateBinaryMeasurements()) {
+ pw.println("ERROR: Failed to refresh info for APEXs.");
+ return -1;
+ }
+ if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
+ pw.println("ERROR: Unable to obtain apex_info at this time.");
+ return -1;
+ }
+
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ default:
+ pw.println("ERROR: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ pw.println("ERROR: Failed to obtain an instance of package manager.");
+ return -1;
+ }
+
+ pw.println("APEX Info:");
+ for (PackageInfo packageInfo : getInstalledApexs()) {
+ String packageName = packageInfo.packageName;
+ pw.println(packageName + ";"
+ + packageInfo.getLongVersionCode() + ":"
+ + mBinaryHashes.get(packageName).toLowerCase());
+
+ if (verbose) {
+ pw.println("Install location: "
+ + packageInfo.applicationInfo.sourceDir);
+ pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
+
+ ModuleInfo moduleInfo;
+ try {
+ moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Is A Module: False");
+ pw.println("");
+ continue;
+ }
+ pw.println("Is A Module: True");
+ printModuleDetails(moduleInfo, pw);
+ pw.println("");
+ }
+ }
+ return 0;
+ }
+
+ private int printAllModules() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ String opt;
+
+ // refresh cache to make sure info is most up-to-date
+ if (!updateBinaryMeasurements()) {
+ pw.println("ERROR: Failed to refresh info for Modules.");
+ return -1;
+ }
+ if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
+ pw.println("ERROR: Unable to obtain module_info at this time.");
+ return -1;
+ }
+
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ default:
+ pw.println("ERROR: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ pw.println("ERROR: Failed to obtain an instance of package manager.");
+ return -1;
+ }
+
+ pw.println("Module Info:");
+ for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
+ String packageName = module.getPackageName();
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.MATCH_APEX);
+ pw.println(packageInfo.packageName + ";"
+ + packageInfo.getLongVersionCode() + ":"
+ + mBinaryHashes.get(packageName).toLowerCase());
+
+ if (verbose) {
+ pw.println("Install location: "
+ + packageInfo.applicationInfo.sourceDir);
+ printModuleDetails(module, pw);
+ pw.println("");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println(packageName);
+ pw.println(packageName
+ + ";ERROR:Unable to find PackageInfo for this module.");
+ if (verbose) {
+ printModuleDetails(module, pw);
+ pw.println("");
+ }
+ continue;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "get": {
+ final String infoType = getNextArg();
+ if (infoType == null) {
+ printHelpMenu();
+ return -1;
+ }
+
+ switch (infoType) {
+ case "image_info":
+ return printSignedImageInfo();
+ case "apex_info":
+ return printAllApexs();
+ case "module_info":
+ return printAllModules();
+ default:
+ pw.println(String.format("ERROR: Unknown info type '%s'",
+ infoType));
+ return 1;
+ }
+ }
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ private void printHelpMenu() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Transparency manager (transparency) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" get image_info [-a]");
+ pw.println(" Print information about loaded image (firmware). Options:");
+ pw.println(" -a: lists all other identifiable partitions.");
+ pw.println("");
+ pw.println(" get apex_info [-v]");
+ pw.println(" Print information about installed APEXs on device.");
+ pw.println(" -v: lists more verbose information about each APEX");
+ pw.println("");
+ pw.println(" get module_info [-v]");
+ pw.println(" Print information about installed modules on device.");
+ pw.println(" -v: lists more verbose information about each module");
+ pw.println("");
+ }
+
+ @Override
+ public void onHelp() {
+ printHelpMenu();
+ }
+ }).exec(this, in, out, err, args, callback, resultReceiver);
+ }
+ }
+ private final BinaryTransparencyServiceImpl mServiceImpl;
+
+ public BinaryTransparencyService(Context context) {
+ super(context);
+ mContext = context;
+ mServiceImpl = new BinaryTransparencyServiceImpl();
+ mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
+ mBinaryHashes = new HashMap<>();
+ mBinaryLastUpdateTimes = new HashMap<>();
+ }
+
+ /**
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
+ */
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.BINARY_TRANSPARENCY_SERVICE, mServiceImpl);
+ Slog.i(TAG, "Started BinaryTransparencyService");
+ } catch (Throwable t) {
+ Slog.e(TAG, "Failed to start BinaryTransparencyService.", t);
+ }
+ }
+
+ /**
+ * Called on each phase of the boot process. Phases before the service's start phase
+ * (as defined in the @Service annotation) are never received.
+ *
+ * @param phase The current boot phase.
+ */
+ @Override
+ public void onBootPhase(int phase) {
+
+ // we are only interested in doing things at PHASE_BOOT_COMPLETED
+ if (phase == PHASE_BOOT_COMPLETED) {
+ // due to potentially long computation that holds up boot time, apex sha computations
+ // are deferred to first call
+ Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
+ getVBMetaDigestInformation();
+ }
+ }
+
+ private void getVBMetaDigestInformation() {
+ mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
+ Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
+ }
+
+ @NonNull
+ private List<PackageInfo> getInstalledApexs() {
+ List<PackageInfo> results = new ArrayList<PackageInfo>();
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+ return results;
+ }
+ List<PackageInfo> allPackages = pm.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+ if (allPackages == null) {
+ Slog.e(TAG, "Error obtaining installed packages (including APEX)");
+ return results;
+ }
+
+ results = allPackages.stream().filter(p -> p.isApex).collect(Collectors.toList());
+ return results;
+ }
+
+
+ /**
+ * Updates the internal data structure with the most current APEX measurements.
+ * @return true if update is successful; false otherwise.
+ */
+ private boolean updateBinaryMeasurements() {
+ if (mBinaryHashes.size() == 0) {
+ Slog.d(TAG, "No apex in cache yet.");
+ doFreshBinaryMeasurements();
+ return true;
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Failed to obtain a valid PackageManager instance.");
+ return false;
+ }
+
+ // We're assuming updates to existing modules and APEXs can happen, but not brand new
+ // ones appearing out of the blue. Thus, we're going to only go through our cache to check
+ // for changes, rather than freshly invoking `getInstalledPackages()` and
+ // `getInstalledModules()`
+ for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
+ String packageName = entry.getKey();
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+ long cachedUpdateTime = entry.getValue();
+
+ if (packageInfo.lastUpdateTime > cachedUpdateTime) {
+ Slog.d(TAG, packageName + " has been updated!");
+ entry.setValue(packageInfo.lastUpdateTime);
+
+ // compute the digest for the updated package
+ String sha256digest = computeSha256DigestOfFile(
+ packageInfo.applicationInfo.sourceDir);
+ if (sha256digest == null) {
+ Slog.e(TAG, "Failed to compute SHA256sum for file at "
+ + packageInfo.applicationInfo.sourceDir);
+ mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
+ } else {
+ mBinaryHashes.put(packageName, sha256digest);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Could not find package with name " + packageName);
+ continue;
+ }
+ }
+
+ return true;
+ }
+
+ private void doFreshBinaryMeasurements() {
+ PackageManager pm = mContext.getPackageManager();
+ Slog.d(TAG, "Obtained package manager");
+
+ // In general, we care about all APEXs, *and* all Modules, which may include some APKs.
+
+ // First, we deal with all installed APEXs.
+ for (PackageInfo packageInfo : getInstalledApexs()) {
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+
+ // compute SHA256 for these APEXs
+ String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
+ if (sha256digest == null) {
+ Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
+ packageInfo.packageName));
+ mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR);
+ } else {
+ mBinaryHashes.put(packageInfo.packageName, sha256digest);
+ }
+ Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
+ packageInfo.lastUpdateTime));
+ mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
+ }
+
+ // Next, get all installed modules from PackageManager - skip over those APEXs we've
+ // processed above
+ for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
+ String packageName = module.getPackageName();
+ if (packageName == null) {
+ Slog.e(TAG, "ERROR: Encountered null package name for module "
+ + module.getApexModuleName());
+ continue;
+ }
+ if (mBinaryHashes.containsKey(module.getPackageName())) {
+ continue;
+ }
+
+ // get PackageInfo for this module
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+
+ // compute SHA256 digest for these modules
+ String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
+ if (sha256digest == null) {
+ Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
+ packageName));
+ mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
+ } else {
+ mBinaryHashes.put(packageName, sha256digest);
+ }
+ Slog.d(TAG, String.format("Last update time for %s: %d", packageName,
+ packageInfo.lastUpdateTime));
+ mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: "
+ + packageName);
+ continue;
+ }
+ }
+ }
+
+ @Nullable
+ private String computeSha256DigestOfFile(@NonNull String pathToFile) {
+ File apexFile = new File(pathToFile);
+
+ try {
+ byte[] apexFileBytes = Files.readAllBytes(apexFile.toPath());
+ return PackageUtils.computeSha256Digest(apexFileBytes);
+ } catch (IOException e) {
+ Slog.e(TAG, String.format("I/O error occurs when reading from %s", pathToFile));
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/CircularQueue.java b/services/core/java/com/android/server/CircularQueue.java
new file mode 100644
index 0000000..aac6752
--- /dev/null
+++ b/services/core/java/com/android/server/CircularQueue.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;
+
+import android.util.ArrayMap;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * CircularQueue of length limit which puts keys in a circular LinkedList and values in an ArrayMap.
+ * @param <K> key
+ * @param <V> value
+ */
+public class CircularQueue<K, V> extends LinkedList<K> {
+ private final int mLimit;
+ private final ArrayMap<K, V> mArrayMap = new ArrayMap<>();
+
+ public CircularQueue(int limit) {
+ this.mLimit = limit;
+ }
+
+ @Override
+ public boolean add(K k) throws IllegalArgumentException {
+ throw new IllegalArgumentException("Call of add(key) prohibited. Please call put(key, "
+ + "value) instead. ");
+ }
+
+ /**
+ * Put a (key|value) pair in the CircularQueue. Only the key will be added to the queue. Value
+ * will be added to the ArrayMap.
+ * @return {@code true} (as specified by {@link Collection#add})
+ */
+ public boolean put(K key, V value) {
+ super.add(key);
+ mArrayMap.put(key, value);
+ while (size() > mLimit) {
+ K removedKey = super.remove();
+ mArrayMap.remove(removedKey);
+ }
+ return true;
+ }
+
+ /**
+ * Removes the element for the provided key from the data structure.
+ * @param key which should be removed
+ * @return the value which was removed
+ */
+ public V removeElement(K key) {
+ super.remove(key);
+ return mArrayMap.remove(key);
+ }
+
+ /**
+ * Retrieve a value from the array.
+ * @param key The key of the value to retrieve.
+ * @return Returns the value associated with the given key,
+ * or null if there is no such key.
+ */
+ public V getElement(K key) {
+ return mArrayMap.get(key);
+ }
+
+ /**
+ * Check whether a key exists in the array.
+ *
+ * @param key The key to search for.
+ * @return Returns true if the key exists, else false.
+ */
+ public boolean containsKey(K key) {
+ return mArrayMap.containsKey(key);
+ }
+
+ /**
+ * Return a {@link java.util.Collection} for iterating over and interacting with all values
+ * in the array map.
+ *
+ * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
+ * requires generating a number of temporary objects and allocates additional state
+ * information associated with the container that will remain for the life of the container.</p>
+ */
+ public Collection<V> values() {
+ return mArrayMap.values();
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 8551d88..39ac5ef 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -34,10 +34,6 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.TrafficStats.UID_TETHERING;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
@@ -58,7 +54,6 @@
import android.net.NetworkStack;
import android.net.NetworkStats;
import android.net.RouteInfo;
-import android.net.TetherStatsParcel;
import android.net.UidRangeParcel;
import android.net.util.NetdService;
import android.os.BatteryStats;
@@ -1286,40 +1281,9 @@
private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
@Override
public NetworkStats getTetherStats(int how) {
- // We only need to return per-UID stats. Per-device stats are already counted by
- // interface counters.
- if (how != STATS_PER_UID) {
- return new NetworkStats(SystemClock.elapsedRealtime(), 0);
- }
-
- final TetherStatsParcel[] tetherStatsVec;
- try {
- tetherStatsVec = mNetdService.tetherGetStats();
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException("problem parsing tethering stats: ", e);
- }
-
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(),
- tetherStatsVec.length);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
-
- for (TetherStatsParcel tetherStats : tetherStatsVec) {
- try {
- entry.iface = tetherStats.iface;
- entry.uid = UID_TETHERING;
- entry.set = SET_DEFAULT;
- entry.tag = TAG_NONE;
- entry.rxBytes = tetherStats.rxBytes;
- entry.rxPackets = tetherStats.rxPackets;
- entry.txBytes = tetherStats.txBytes;
- entry.txPackets = tetherStats.txPackets;
- stats.combineValues(entry);
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalStateException("invalid tethering stats " + e);
- }
- }
-
- return stats;
+ // Remove the implementation of NetdTetheringStatsProvider#getTetherStats
+ // since all callers are migrated to use INetd#tetherGetStats directly.
+ throw new UnsupportedOperationException();
}
@Override
@@ -1330,20 +1294,9 @@
@Override
public NetworkStats getNetworkStatsTethering(int how) {
- NetworkStack.checkNetworkStackPermission(mContext);
-
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
- synchronized (mTetheringStatsProviders) {
- for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
- try {
- stats.combineAllValues(provider.getTetherStats(how));
- } catch (RemoteException e) {
- Log.e(TAG, "Problem reading tethering stats from " +
- mTetheringStatsProviders.get(provider) + ": " + e);
- }
- }
- }
- return stats;
+ // Remove the implementation of getNetworkStatsTethering since all callers are migrated
+ // to use INetd#tetherGetStats directly.
+ throw new UnsupportedOperationException();
}
@Override
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 71b463a..4129feb 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -10,9 +10,6 @@
# Userspace reboot
per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com
-# Sensor Privacy
-per-file SensorPrivacyService.java = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
-
# ServiceWatcher
per-file ServiceWatcher.java = sooniln@google.com
@@ -22,6 +19,7 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppOp* = file:/core/java/android/permission/OWNERS
per-file *Battery* = file:/BATTERY_STATS_OWNERS
+per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8887108..b5c0a67 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2975,9 +2975,21 @@
Binder.restoreCallingIdentity(origId);
}
+ notifyBindingServiceEventLocked(callerApp, callingPackage);
+
return 1;
}
+ @GuardedBy("mAm")
+ private void notifyBindingServiceEventLocked(ProcessRecord callerApp, String callingPackage) {
+ final ApplicationInfo ai = callerApp.info;
+ final String callerPackage = ai != null ? ai.packageName : callingPackage;
+ if (callerPackage != null) {
+ mAm.mHandler.obtainMessage(ActivityManagerService.DISPATCH_BINDING_SERVICE_EVENT,
+ callerApp.uid, 0, callerPackage).sendToTarget();
+ }
+ }
+
private void maybeLogBindCrossProfileService(
int userId, String callingPackage, int callingUid) {
if (UserHandle.isCore(callingUid)) {
@@ -5138,29 +5150,6 @@
return didSomething;
}
- void makeServicesNonForegroundLocked(final String pkg, final @UserIdInt int userId) {
- final ServiceMap smap = mServiceMap.get(userId);
- if (smap != null) {
- ArrayList<ServiceRecord> fgsList = new ArrayList<>();
- for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
- final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
- if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
- fgsList.add(sr);
- }
- }
-
- final int numServices = fgsList.size();
- if (DEBUG_FOREGROUND_SERVICE) {
- Slog.i(TAG_SERVICE, "Forcing " + numServices + " services out of foreground in u"
- + userId + "/" + pkg);
- }
- for (int i = 0; i < numServices; i++) {
- final ServiceRecord sr = fgsList.get(i);
- setServiceForegroundInnerLocked(sr, 0, null, Service.STOP_FOREGROUND_REMOVE, 0);
- }
- }
- }
-
@GuardedBy("mAm")
private void signalForegroundServiceObserversLocked(ServiceRecord r) {
final int num = mFgsObservers.beginBroadcast();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f67e732..ae4f79e7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -153,8 +153,12 @@
import android.app.ActivityManager;
import android.app.ActivityManager.PendingIntentInfo;
import android.app.ActivityManager.ProcessCapability;
+import android.app.ActivityManager.RestrictionLevel;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.ActivityThread;
import android.app.AnrController;
@@ -186,7 +190,6 @@
import android.app.PendingIntent;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
-import android.app.PropertyInvalidatedCache;
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
@@ -233,11 +236,9 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
import android.content.pm.ResolveInfo;
-import com.android.server.pm.pkg.SELinuxUtil;
import android.content.pm.ServiceInfo;
import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -396,6 +397,8 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.SELinuxUtil;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -438,6 +441,7 @@
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -1373,6 +1377,25 @@
= new ProcessMap<ArrayList<ProcessRecord>>();
/**
+ * The list of foreground service state change listeners.
+ */
+ @GuardedBy("this")
+ final ArrayList<ForegroundServiceStateListener> mForegroundServiceStateListeners =
+ new ArrayList<>();
+
+ /**
+ * The list of broadcast event listeners.
+ */
+ final CopyOnWriteArrayList<BroadcastEventListener> mBroadcastEventListeners =
+ new CopyOnWriteArrayList<>();
+
+ /**
+ * The list of bind service event listeners.
+ */
+ final CopyOnWriteArrayList<BindServiceEventListener> mBindServiceEventListeners =
+ new CopyOnWriteArrayList<>();
+
+ /**
* Set if the systemServer made a call to enterSafeMode.
*/
@GuardedBy("this")
@@ -1456,6 +1479,8 @@
final UidObserverController mUidObserverController;
+ final AppRestrictionController mAppRestrictionController;
+
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
@@ -1512,6 +1537,8 @@
static final int KILL_APP_ZYGOTE_MSG = 71;
static final int BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG = 72;
static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
+ static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
+ static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1829,6 +1856,14 @@
((ContentProviderRecord) msg.obj).onProviderPublishStatusLocked(false);
}
} break;
+ case DISPATCH_SENDING_BROADCAST_EVENT: {
+ mBroadcastEventListeners.forEach(l ->
+ l.onSendingBroadcast((String) msg.obj, msg.arg1));
+ } break;
+ case DISPATCH_BINDING_SERVICE_EVENT: {
+ mBindServiceEventListeners.forEach(l ->
+ l.onBindingService((String) msg.obj, msg.arg1));
+ } break;
}
}
}
@@ -2269,6 +2304,7 @@
mPendingIntentController = hasHandlerThread
? new PendingIntentController(handlerThread.getLooper(), mUserController,
mConstants) : null;
+ mAppRestrictionController = new AppRestrictionController(mContext, this);
mProcStartHandlerThread = null;
mProcStartHandler = null;
mHiddenApiBlacklist = null;
@@ -2378,6 +2414,8 @@
mPendingIntentController = new PendingIntentController(
mHandlerThread.getLooper(), mUserController, mConstants);
+ mAppRestrictionController = new AppRestrictionController(mContext, this);
+
mUseFifoUiScheduling = SystemProperties.getInt("sys.use_fifo_ui", 0) != 0;
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
@@ -3782,10 +3820,10 @@
}
@Override
- public void makeServicesNonForeground(final String packageName, int userId) {
+ public void stopAppForUser(final String packageName, int userId) {
if (checkCallingPermission(MANAGE_ACTIVITY_TASKS)
!= PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: makeServicesNonForeground() from pid="
+ String msg = "Permission Denial: stopAppForUser() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ " requires " + MANAGE_ACTIVITY_TASKS;
@@ -3795,10 +3833,10 @@
final int callingPid = Binder.getCallingPid();
userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
- userId, true, ALLOW_FULL_ONLY, "makeServicesNonForeground", null);
+ userId, true, ALLOW_FULL_ONLY, "stopAppForUser", null);
final long callingId = Binder.clearCallingIdentity();
try {
- makeServicesNonForegroundUnchecked(packageName, userId);
+ stopAppForUserInternal(packageName, userId);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -4133,13 +4171,6 @@
}
}
- private void makeServicesNonForegroundUnchecked(final String packageName,
- final @UserIdInt int userId) {
- synchronized (this) {
- mServices.makeServicesNonForegroundLocked(packageName, userId);
- }
- }
-
@GuardedBy("this")
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
@@ -4266,6 +4297,41 @@
mProcessList.killAppZygotesLocked(packageName, appId, userId, true /* force */);
}
+ void stopAppForUserInternal(final String packageName, @UserIdInt final int userId) {
+ final int uid = getPackageManagerInternal().getPackageUid(packageName,
+ MATCH_DEBUG_TRIAGED_MISSING | MATCH_ANY_USER, userId);
+ if (uid < 0) {
+ Slog.w(TAG, "Asked to stop " + packageName + "/u" + userId
+ + " but does not exist in that user");
+ return;
+ }
+ Slog.i(TAG, "Stopping app for user: " + packageName + "/" + userId);
+
+ // A specific subset of the work done in forceStopPackageLocked(), because we are
+ // intentionally not rendering the app nonfunctional; we're just halting its current
+ // execution.
+ final int appId = UserHandle.getAppId(uid);
+ synchronized (this) {
+ synchronized (mProcLock) {
+ mAtmInternal.onForceStopPackage(packageName, true, false, userId);
+
+ mProcessList.killPackageProcessesLSP(packageName, appId, userId,
+ ProcessList.INVALID_ADJ, true, false, true,
+ false, true /* setRemoved */, false,
+ ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "fully stop " + packageName + "/" + userId + " by user request");
+ }
+
+ mServices.bringDownDisabledPackageServicesLocked(
+ packageName, null, userId, false, true);
+
+ if (mBooted) {
+ mAtmInternal.resumeTopActivities(true);
+ }
+ }
+ }
+
@GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
@@ -7722,6 +7788,7 @@
mUserController.onSystemReady();
mAppOpsService.systemReady();
mProcessList.onSystemReady();
+ mAppRestrictionController.onSystemReady();
mSystemReady = true;
t.traceEnd();
}
@@ -9005,6 +9072,10 @@
}
mComponentAliasResolver.dump(pw);
}
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ mAppRestrictionController.dump(pw, "");
+ }
}
/**
@@ -14807,8 +14878,16 @@
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
int fgServiceTypes, boolean oomAdj) {
final ProcessServiceRecord psr = proc.mServices;
- if (isForeground != psr.hasForegroundServices()
+ final boolean foregroundStateChanged = isForeground != psr.hasForegroundServices();
+ if (foregroundStateChanged
|| psr.getForegroundServiceTypes() != fgServiceTypes) {
+ if (foregroundStateChanged) {
+ // Notify internal listeners.
+ for (int i = mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
+ mForegroundServiceStateListeners.get(i).onForegroundServiceStateChanged(
+ proc.info.packageName, proc.info.uid, proc.getPid(), isForeground);
+ }
+ }
psr.setHasForegroundServices(isForeground, fgServiceTypes);
ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
proc.info.uid);
@@ -15807,6 +15886,7 @@
synchronized (mProcLock) {
mDeviceIdleAllowlist = allAppids;
mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+ mAppRestrictionController.setDeviceIdleAllowlist(allAppids, exceptIdleAppids);
}
}
}
@@ -16567,8 +16647,8 @@
}
@Override
- public void makeServicesNonForeground(String pkg, int userId) {
- ActivityManagerService.this.makeServicesNonForegroundUnchecked(pkg, userId);
+ public void stopAppForUser(String pkg, @UserIdInt int userId) {
+ ActivityManagerService.this.stopAppForUserInternal(pkg, userId);
}
@Override
@@ -16794,6 +16874,47 @@
public void setStopUserOnSwitch(int value) {
ActivityManagerService.this.setStopUserOnSwitch(value);
}
+
+ @Override
+ public @RestrictionLevel int getRestrictionLevel(int uid) {
+ return mAppRestrictionController.getRestrictionLevel(uid);
+ }
+
+ @Override
+ public @RestrictionLevel int getRestrictionLevel(String pkg, @UserIdInt int userId) {
+ return mAppRestrictionController.getRestrictionLevel(pkg, userId);
+ }
+
+ @Override
+ public boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
+ return mAppRestrictionController.isBgAutoRestrictedBucketFeatureFlagEnabled();
+ }
+
+ @Override
+ public void addAppBackgroundRestrictionListener(
+ @NonNull ActivityManagerInternal.AppBackgroundRestrictionListener listener) {
+ mAppRestrictionController.addAppBackgroundRestrictionListener(listener);
+ }
+
+ @Override
+ public void addForegroundServiceStateListener(
+ @NonNull ForegroundServiceStateListener listener) {
+ synchronized (ActivityManagerService.this) {
+ mForegroundServiceStateListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void addBroadcastEventListener(@NonNull BroadcastEventListener listener) {
+ // It's a CopyOnWriteArrayList, so no lock is needed.
+ mBroadcastEventListeners.add(listener);
+ }
+
+ @Override
+ public void addBindServiceEventListener(@NonNull BindServiceEventListener listener) {
+ // It's a CopyOnWriteArrayList, so no lock is needed.
+ mBindServiceEventListeners.add(listener);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@ -16961,6 +17082,14 @@
}
}
+ @Override
+ @ReasonCode
+ public int getBackgroundRestrictionExemptionReason(int uid) {
+ enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+ "getBackgroundRestrictionExemptionReason()");
+ return mAppRestrictionController.getBackgroundRestrictionExemptionReason(uid);
+ }
+
/**
* Force the settings cache to be loaded
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index b6a0ec4..043ea08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -240,8 +240,8 @@
return runBugReport(pw);
case "force-stop":
return runForceStop(pw);
- case "stop-fgs":
- return runStopForegroundServices(pw);
+ case "stop-app":
+ return runStopApp(pw);
case "fgs-notification-rate-limit":
return runFgsNotificationRateLimit(pw);
case "crash":
@@ -338,6 +338,8 @@
return runGetIsolatedProcesses(pw);
case "set-stop-user-on-switch":
return runSetStopUserOnSwitch(pw);
+ case "set-bg-abusive-uids":
+ return runSetBgAbusiveUids(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -1128,7 +1130,7 @@
return 0;
}
- int runStopForegroundServices(PrintWriter pw) throws RemoteException {
+ int runStopApp(PrintWriter pw) throws RemoteException {
int userId = UserHandle.USER_SYSTEM;
String opt;
@@ -1140,7 +1142,7 @@
return -1;
}
}
- mInterface.makeServicesNonForeground(getNextArgRequired(), userId);
+ mInterface.stopAppForUser(getNextArgRequired(), userId);
return 0;
}
@@ -3225,6 +3227,43 @@
return 0;
}
+ // TODO(b/203105544) STOPSHIP - For debugging only, to be removed before shipping.
+ private int runSetBgAbusiveUids(PrintWriter pw) throws RemoteException {
+ final String arg = getNextArg();
+ final AppBatteryTracker batteryTracker =
+ mInternal.mAppRestrictionController.getAppStateTracker(AppBatteryTracker.class);
+ if (batteryTracker == null) {
+ getErrPrintWriter().println("Unable to get bg battery tracker");
+ return -1;
+ }
+ if (arg == null) {
+ batteryTracker.mDebugUidPercentages.clear();
+ return 0;
+ }
+ String[] pairs = arg.split(",");
+ int[] uids = new int[pairs.length];
+ double[] values = new double[pairs.length];
+ try {
+ for (int i = 0; i < pairs.length; i++) {
+ String[] pair = pairs[i].split("=");
+ if (pair.length != 2) {
+ getErrPrintWriter().println("Malformed input");
+ return -1;
+ }
+ uids[i] = Integer.parseInt(pair[0]);
+ values[i] = Double.parseDouble(pair[1]);
+ }
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Malformed input");
+ return -1;
+ }
+ batteryTracker.mDebugUidPercentages.clear();
+ for (int i = 0; i < pairs.length; i++) {
+ batteryTracker.mDebugUidPercentages.put(uids[i], values[i]);
+ }
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
@@ -3562,6 +3601,8 @@
pw.println(" Sets whether the current user (and its profiles) should be stopped"
+ " when switching to a different user.");
pw.println(" Without arguments, it resets to the value defined by platform.");
+ pw.println(" set-bg-abusive-uids [uid=percentage][,uid=percentage...]");
+ pw.println(" Force setting the battery usage of the given UID.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
new file mode 100644
index 0000000..75de3a1
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
+import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.BaseAppStateDurationsTracker.EventListener;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the current drains that should be excluded from the current drain
+ * accounting, examples are media playback, location sharing, etc.
+ *
+ * <p>
+ * Note: as the {@link AppBatteryTracker#getUidBatteryUsage} could return the battery usage data
+ * from most recent polling due to throttling, the battery usage of a certain event here
+ * would NOT be the exactly same amount that it actually costs.
+ * </p>
+ */
+final class AppBatteryExemptionTracker
+ extends BaseAppStateDurationsTracker<AppBatteryExemptionPolicy, UidBatteryStates>
+ implements BaseAppStateEvents.Factory<UidBatteryStates>, EventListener {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryExemptionTracker" : TAG_AM;
+
+ private static final boolean DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER = false;
+
+ // As it's a UID-based tracker, anywhere which requires a package name, use this default name.
+ private static final String DEFAULT_NAME = "";
+
+ AppBatteryExemptionTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppBatteryExemptionTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppBatteryExemptionPolicy>> injector,
+ Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mInjector.setPolicy(new AppBatteryExemptionPolicy(mInjector, this));
+ }
+
+ @Override
+ void onSystemReady() {
+ super.onSystemReady();
+ mAppRestrictionController.forEachTracker(tracker -> {
+ if (tracker instanceof BaseAppStateDurationsTracker) {
+ ((BaseAppStateDurationsTracker) tracker).registerEventListener(this);
+ }
+ });
+ }
+
+ @Override
+ public UidBatteryStates createAppStateEvents(int uid, String packageName) {
+ return new UidBatteryStates(uid, TAG, mInjector.getPolicy());
+ }
+
+ @Override
+ public UidBatteryStates createAppStateEvents(UidBatteryStates other) {
+ return new UidBatteryStates(other);
+ }
+
+ @Override
+ public void onNewEvent(int uid, String packageName, boolean start, long now, int eventType) {
+ if (!mInjector.getPolicy().isEnabled()) {
+ return;
+ }
+ final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+ synchronized (mLock) {
+ UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
+ if (pkg == null) {
+ pkg = createAppStateEvents(uid, DEFAULT_NAME);
+ mPkgEvents.put(uid, DEFAULT_NAME, pkg);
+ }
+ pkg.addEvent(start, now, batteryUsage, eventType);
+ }
+ }
+
+ private void onTrackerEnabled(boolean enabled) {
+ if (!enabled) {
+ synchronized (mLock) {
+ mPkgEvents.clear();
+ }
+ }
+ }
+
+ /**
+ * @return The to-be-exempted battery usage of the given UID in the given duration; it could
+ * be considered as "exempted" due to various use cases, i.e. media playback.
+ */
+ double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+ if (!mInjector.getPolicy().isEnabled()) {
+ return 0.0d;
+ }
+ Pair<Double, Double> result;
+ synchronized (mLock) {
+ final UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME);
+ if (pkg == null) {
+ return 0.0d;
+ }
+ result = pkg.getBatteryUsageSince(since, now);
+ }
+ if (result.second > 0.0d) {
+ // We have an open event (just start, no stop), get the battery usage till now.
+ final double batteryUsage = mAppRestrictionController.getUidBatteryUsage(uid);
+ return result.first + batteryUsage - result.second;
+ }
+ return result.first;
+ }
+
+ static final class UidBatteryStates extends BaseAppStateDurations<UidStateEventWithBattery> {
+ UidBatteryStates(int uid, @NonNull String tag,
+ @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, DEFAULT_NAME, EVENT_NUM, tag, maxTrackingDurationConfig);
+ }
+
+ UidBatteryStates(@NonNull UidBatteryStates other) {
+ super(other);
+ }
+
+ /**
+ * @param start {@code true} if it's a start event.
+ * @param now The timestamp when this event occurred.
+ * @param batteryUsage The background current drain since the system boots.
+ * @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker.
+ */
+ void addEvent(boolean start, long now, double batteryUsage, int eventType) {
+ if (start) {
+ addEvent(start, new UidStateEventWithBattery(start, now, batteryUsage, null),
+ eventType);
+ } else {
+ final UidStateEventWithBattery last = getLastEvent(eventType);
+ if (last == null || !last.isStart()) {
+ if (DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER) {
+ Slog.wtf(TAG, "Unexpected stop event " + eventType);
+ }
+ return;
+ }
+ addEvent(start, new UidStateEventWithBattery(start, now,
+ batteryUsage - last.getBatteryUsage(), last), eventType);
+ }
+ }
+
+ UidStateEventWithBattery getLastEvent(int eventType) {
+ return mEvents[eventType] != null ? mEvents[eventType].peekLast() : null;
+ }
+
+ /**
+ * @return The pair of bg battery usage of given duration; the first value in the pair
+ * is the aggregated battery usage of all event pairs in this duration; while
+ * the second value is the battery usage since the system boots, if there is
+ * an open event(just start, no stop) at the end of the duration.
+ */
+ Pair<Double, Double> getBatteryUsageSince(long since, long now, int eventType) {
+ return getBatteryUsageSince(since, now, mEvents[eventType]);
+ }
+
+ private Pair<Double, Double> getBatteryUsageSince(long since, long now,
+ LinkedList<UidStateEventWithBattery> events) {
+ if (events == null || events.size() == 0) {
+ return Pair.create(0.0d, 0.0d);
+ }
+ double batteryUsage = 0.0d;
+ UidStateEventWithBattery lastEvent = null;
+ for (UidStateEventWithBattery event : events) {
+ lastEvent = event;
+ if (event.getTimestamp() < since || event.isStart()) {
+ continue;
+ }
+ batteryUsage += event.getBatteryUsage(since, Math.min(now, event.getTimestamp()));
+ if (now <= event.getTimestamp()) {
+ break;
+ }
+ }
+ return Pair.create(batteryUsage, lastEvent.isStart() ? lastEvent.getBatteryUsage() : 0);
+ }
+
+ /**
+ * @return The aggregated battery usage amongst all the event types we're tracking.
+ */
+ Pair<Double, Double> getBatteryUsageSince(long since, long now) {
+ LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
+ for (int i = 0; i < mEvents.length; i++) {
+ result = add(result, mEvents[i]);
+ }
+ return getBatteryUsageSince(since, now, result);
+ }
+
+ /**
+ * Merge the two given duration table and return the result.
+ */
+ @VisibleForTesting
+ @Override
+ LinkedList<UidStateEventWithBattery> add(LinkedList<UidStateEventWithBattery> durations,
+ LinkedList<UidStateEventWithBattery> otherDurations) {
+ if (otherDurations == null || otherDurations.size() == 0) {
+ return durations;
+ }
+ if (durations == null || durations.size() == 0) {
+ return (LinkedList<UidStateEventWithBattery>) otherDurations.clone();
+ }
+ final Iterator<UidStateEventWithBattery> itl = durations.iterator();
+ final Iterator<UidStateEventWithBattery> itr = otherDurations.iterator();
+ UidStateEventWithBattery l = itl.next(), r = itr.next();
+ LinkedList<UidStateEventWithBattery> dest = new LinkedList<>();
+ boolean actl = false, actr = false, overlapping = false;
+ double batteryUsage = 0.0d;
+ long recentActTs = 0, overlappingDuration = 0;
+ for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+ lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+ final boolean actCur = actl || actr;
+ final UidStateEventWithBattery earliest;
+ if (lts == rts) {
+ earliest = l;
+ // we'll deal with the double counting problem later.
+ batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+ batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+ overlappingDuration += overlapping && (actl || actr)
+ ? (lts - recentActTs) : 0;
+ actl = !actl;
+ actr = !actr;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ } else if (lts < rts) {
+ earliest = l;
+ batteryUsage += actl ? l.getBatteryUsage() : 0.0d;
+ overlappingDuration += overlapping && actl ? (lts - recentActTs) : 0;
+ actl = !actl;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ } else {
+ earliest = r;
+ batteryUsage += actr ? r.getBatteryUsage() : 0.0d;
+ overlappingDuration += overlapping && actr ? (rts - recentActTs) : 0;
+ actr = !actr;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ }
+ overlapping = actl && actr;
+ if (actl || actr) {
+ recentActTs = earliest.getTimestamp();
+ }
+ if (actCur != (actl || actr)) {
+ final UidStateEventWithBattery event =
+ (UidStateEventWithBattery) earliest.clone();
+ if (actCur) {
+ // It's an stop/end event, update the start timestamp and batteryUsage.
+ final UidStateEventWithBattery lastEvent = dest.peekLast();
+ final long startTs = lastEvent.getTimestamp();
+ final long duration = event.getTimestamp() - startTs;
+ final long durationWithOverlapping = duration + overlappingDuration;
+ // Get the proportional batteryUsage.
+ if (durationWithOverlapping != 0) {
+ batteryUsage *= duration * 1.0d / durationWithOverlapping;
+ } else {
+ batteryUsage = 0.0d;
+ }
+ event.update(lastEvent, batteryUsage);
+ batteryUsage = 0.0d;
+ overlappingDuration = 0;
+ }
+ dest.add(event);
+ }
+ }
+ return dest;
+ }
+ }
+
+ private void trimDurations() {
+ final long now = SystemClock.elapsedRealtime();
+ trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ // We're dumping the data in AppBatteryTracker actually, so just dump the policy here.
+ mInjector.getPolicy().dump(pw, prefix);
+ }
+
+ /**
+ * A basic event marking a certain event, i.e., a FGS start/stop;
+ * it'll record the background battery usage data over the start/stop.
+ */
+ static final class UidStateEventWithBattery extends BaseTimeEvent {
+ /**
+ * Whether or not this is a start event.
+ */
+ private boolean mIsStart;
+
+ /**
+ * The known background battery usage; it will be the total bg battery usage since
+ * the system boots if the {@link #mIsStart} is true, but will be the delta of the bg
+ * battery usage since the start event if the {@link #mIsStart} is false.
+ */
+ private double mBatteryUsage;
+
+ /**
+ * The peer event of this pair (a pair of start/stop events).
+ */
+ private @Nullable UidStateEventWithBattery mPeer;
+
+ UidStateEventWithBattery(boolean isStart, long now, double batteryUsage,
+ @Nullable UidStateEventWithBattery peer) {
+ super(now);
+ mIsStart = isStart;
+ mBatteryUsage = batteryUsage;
+ mPeer = peer;
+ if (peer != null) {
+ peer.mPeer = this;
+ }
+ }
+
+ UidStateEventWithBattery(UidStateEventWithBattery other) {
+ super(other);
+ mIsStart = other.mIsStart;
+ mBatteryUsage = other.mBatteryUsage;
+ // Don't copy the peer object though.
+ }
+
+ @Override
+ void trimTo(long timestamp) {
+ // We don't move the stop event.
+ if (!mIsStart || timestamp < mTimestamp) {
+ return;
+ }
+ if (mPeer != null) {
+ // Reduce the bg battery usage proportionally.
+ final double batteryUsage = mPeer.getBatteryUsage();
+ mPeer.mBatteryUsage = mPeer.getBatteryUsage(timestamp, mPeer.mTimestamp);
+ // Update the battery data of the start event too.
+ mBatteryUsage += batteryUsage - mPeer.mBatteryUsage;
+ }
+ mTimestamp = timestamp;
+ }
+
+ void update(@NonNull UidStateEventWithBattery peer, double batteryUsage) {
+ mPeer = peer;
+ peer.mPeer = this;
+ mBatteryUsage = batteryUsage;
+ }
+
+ boolean isStart() {
+ return mIsStart;
+ }
+
+ double getBatteryUsage(long start, long end) {
+ if (mIsStart || start >= mTimestamp || end <= start) {
+ return 0.0d;
+ }
+ start = Math.max(start, mPeer.mTimestamp);
+ end = Math.min(end, mTimestamp);
+ final long totalDur = mTimestamp - mPeer.mTimestamp;
+ final long inputDur = end - start;
+ return totalDur != 0 ? mBatteryUsage * (1.0d * inputDur) / totalDur : 0.0d;
+ }
+
+ double getBatteryUsage() {
+ return mBatteryUsage;
+ }
+
+ @Override
+ public Object clone() {
+ return new UidStateEventWithBattery(this);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (other.getClass() != UidStateEventWithBattery.class) {
+ return false;
+ }
+ final UidStateEventWithBattery otherEvent = (UidStateEventWithBattery) other;
+ return otherEvent.mIsStart == mIsStart
+ && otherEvent.mTimestamp == mTimestamp
+ && Double.compare(otherEvent.mBatteryUsage, mBatteryUsage) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return (Boolean.hashCode(mIsStart) * 31
+ + Long.hashCode(mTimestamp)) * 31
+ + Double.hashCode(mBatteryUsage);
+ }
+ }
+
+ static final class AppBatteryExemptionPolicy
+ extends BaseAppStateEventsPolicy<AppBatteryExemptionTracker> {
+ /**
+ * Whether or not we should enable the exemption of certain battery drains.
+ */
+ static final String KEY_BG_BATTERY_EXEMPTION_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "battery_exemption_enabled";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+
+ AppBatteryExemptionPolicy(@NonNull Injector injector,
+ @NonNull AppBatteryExemptionTracker tracker) {
+ super(injector, tracker,
+ KEY_BG_BATTERY_EXEMPTION_ENABLED, DEFAULT_BG_BATTERY_EXEMPTION_ENABLED,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ }
+
+ @Override
+ public void onMaxTrackingDurationChanged(long maxDuration) {
+ mTracker.mBgHandler.post(mTracker::trimDurations);
+ }
+
+ @Override
+ public void onTrackerEnabled(boolean enabled) {
+ mTracker.onTrackerEnabled(enabled);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BATTERY EXEMPTION TRACKER POLICY SETTINGS:");
+ final String indent = " ";
+ prefix = indent + prefix;
+ super.dump(pw, prefix);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
new file mode 100644
index 0000000..8a21a0f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -0,0 +1,1178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.isLowRamDeviceStatic;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.util.TimeUtils.formatTime;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager.RestrictionLevel;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsInternal;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseDoubleArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
+import com.android.server.am.BaseAppStateTracker.Injector;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The battery usage tracker for apps, currently we are focusing on background + FGS battery here.
+ */
+final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
+ implements UidBatteryUsageProvider {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryTracker" : TAG_AM;
+
+ static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
+
+ // As we don't support realtime per-UID battery usage stats yet, we're polling the stats
+ // in a regular time basis.
+ private final long mBatteryUsageStatsPollingIntervalMs;
+
+ // The timestamp when this system_server was started.
+ private long mBootTimestamp;
+
+ static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG = 30 * ONE_MINUTE; // 30 mins
+ static final long BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG = 2_000L; // 2s
+
+ private final long mBatteryUsageStatsPollingMinIntervalMs;
+
+ /**
+ * The battery stats query is expensive, so we'd throttle the query.
+ */
+ static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG = 5 * ONE_MINUTE; // 5 mins
+ static final long BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG = 2_000L; // 2s
+
+ static final BatteryConsumer.Dimensions BATT_DIMEN_FG =
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND);
+ static final BatteryConsumer.Dimensions BATT_DIMEN_BG =
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_BACKGROUND);
+ static final BatteryConsumer.Dimensions BATT_DIMEN_FGS =
+ new BatteryConsumer.Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_FOREGROUND_SERVICE);
+
+ private final Runnable mBgBatteryUsageStatsPolling = this::updateBatteryUsageStatsAndCheck;
+ private final Runnable mBgBatteryUsageStatsCheck = this::checkBatteryUsageStats;
+
+ /**
+ * This tracks the user ids which are or were active during the last polling window,
+ * the index is the user id, and the value is if it's still running or not by now.
+ */
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mActiveUserIdStates = new SparseBooleanArray();
+
+ /**
+ * When was the last battery usage sampled.
+ */
+ @GuardedBy("mLock")
+ private long mLastBatteryUsageSamplingTs;
+
+ /**
+ * Whether or not there is an ongoing battery stats update.
+ */
+ @GuardedBy("mLock")
+ private boolean mBatteryUsageStatsUpdatePending;
+
+ /**
+ * The current known battery usage data for each UID, since the system boots.
+ */
+ @GuardedBy("mLock")
+ private final SparseDoubleArray mUidBatteryUsage = new SparseDoubleArray();
+
+ /**
+ * The battery usage for each UID, in the rolling window of the past.
+ */
+ @GuardedBy("mLock")
+ private final SparseDoubleArray mUidBatteryUsageInWindow = new SparseDoubleArray();
+
+ /**
+ * The uid battery usage stats data from our last query, it does not include snapshot data.
+ */
+ // No lock is needed.
+ private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray();
+
+ // No lock is needed.
+ private final SparseDoubleArray mTmpUidBatteryUsage = new SparseDoubleArray();
+
+ // No lock is needed.
+ private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray();
+
+ // No lock is needed.
+ private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>();
+
+ /**
+ * The start timestamp of the battery usage stats result from our last query.
+ */
+ // No lock is needed.
+ private long mLastUidBatteryUsageStartTs;
+
+ // For debug only.
+ final SparseDoubleArray mDebugUidPercentages = new SparseDoubleArray();
+
+ AppBatteryTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppBatteryTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppBatteryPolicy>> injector,
+ Object outerContext) {
+ super(context, controller, injector, outerContext);
+ if (injector == null) {
+ mBatteryUsageStatsPollingIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
+ ? BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG
+ : BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG;
+ mBatteryUsageStatsPollingMinIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER
+ ? BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG
+ : BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_LONG;
+ } else {
+ mBatteryUsageStatsPollingIntervalMs = BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG;
+ mBatteryUsageStatsPollingMinIntervalMs =
+ BATTERY_USAGE_STATS_POLLING_MIN_INTERVAL_MS_DEBUG;
+ }
+ mInjector.setPolicy(new AppBatteryPolicy(mInjector, this));
+ }
+
+ @Override
+ void onSystemReady() {
+ super.onSystemReady();
+ final UserManagerInternal um = mInjector.getUserManagerInternal();
+ final int[] userIds = um.getUserIds();
+ for (int userId : userIds) {
+ if (um.isUserRunning(userId)) {
+ synchronized (mLock) {
+ mActiveUserIdStates.put(userId, true);
+ }
+ }
+ }
+ mBootTimestamp = mInjector.currentTimeMillis();
+ scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
+ }
+
+ private void scheduleBatteryUsageStatsUpdateIfNecessary(long delay) {
+ if (mInjector.getPolicy().isEnabled()) {
+ synchronized (mLock) {
+ if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
+ mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
+ }
+ }
+ }
+ }
+
+ @Override
+ void onUserStarted(final @UserIdInt int userId) {
+ synchronized (mLock) {
+ mActiveUserIdStates.put(userId, true);
+ }
+ }
+
+ @Override
+ void onUserStopped(final @UserIdInt int userId) {
+ synchronized (mLock) {
+ mActiveUserIdStates.put(userId, false);
+ }
+ }
+
+ @Override
+ void onUserRemoved(final @UserIdInt int userId) {
+ synchronized (mLock) {
+ mActiveUserIdStates.delete(userId);
+ for (int i = mUidBatteryUsage.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mUidBatteryUsage.keyAt(i)) == userId) {
+ mUidBatteryUsage.removeAt(i);
+ }
+ }
+ for (int i = mUidBatteryUsageInWindow.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mUidBatteryUsageInWindow.keyAt(i)) == userId) {
+ mUidBatteryUsageInWindow.removeAt(i);
+ }
+ }
+ }
+ }
+
+ @Override
+ void onUidRemoved(final int uid) {
+ synchronized (mLock) {
+ mUidBatteryUsage.delete(uid);
+ mUidBatteryUsageInWindow.delete(uid);
+ }
+ }
+
+ @Override
+ void onUserInteractionStarted(String packageName, int uid) {
+ mInjector.getPolicy().onUserInteractionStarted(packageName, uid);
+ }
+
+ @Override
+ void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+ mInjector.getPolicy().onBackgroundRestrictionChanged(uid, pkgName, restricted);
+ }
+
+ /**
+ * @return The total battery usage of the given UID since the system boots.
+ *
+ * <p>
+ * Note: as there are throttling in polling the battery usage stats by
+ * the {@link #mBatteryUsageStatsPollingMinIntervalMs}, the returned data here
+ * could be either from the most recent polling, or the very fresh one - if the most recent
+ * polling is outdated, it'll trigger an immediate update.
+ * </p>
+ */
+ @Override
+ public double getUidBatteryUsage(int uid) {
+ final long now = mInjector.currentTimeMillis();
+ final boolean updated = updateBatteryUsageStatsIfNecessary(now, false);
+ synchronized (mLock) {
+ if (updated) {
+ // We just got fresh data, schedule a check right a way.
+ mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+ if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsCheck)) {
+ mBgHandler.post(mBgBatteryUsageStatsCheck);
+ }
+ }
+ return mUidBatteryUsage.get(uid, 0.0d);
+ }
+ }
+
+ private void updateBatteryUsageStatsAndCheck() {
+ final long now = mInjector.currentTimeMillis();
+ if (updateBatteryUsageStatsIfNecessary(now, false)) {
+ checkBatteryUsageStats();
+ } else {
+ // We didn't do the battery stats update above, schedule a check later.
+ scheduleBatteryUsageStatsUpdateIfNecessary(
+ mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+ }
+ }
+
+ private void checkBatteryUsageStats() {
+ final long now = SystemClock.elapsedRealtime();
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ try {
+ final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+ final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+ for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+ final int uid = uidConsumers.keyAt(i);
+ final double actualUsage = uidConsumers.valueAt(i);
+ final double exemptedUsage = mAppRestrictionController
+ .getUidBatteryExemptedUsageSince(uid, since, now);
+ // It's possible the exemptedUsage could be larger than actualUsage,
+ // as the former one is an approximate value.
+ final double bgUsage = Math.max(0.0d, actualUsage - exemptedUsage);
+ final double percentage = bgPolicy.getPercentage(uid, bgUsage);
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, String.format(
+ "UID %d: %.3f mAh (or %4.2f%%) %.3f %.3f over the past %s",
+ uid, bgUsage, percentage, exemptedUsage, actualUsage,
+ TimeUtils.formatDuration(bgPolicy.mBgCurrentDrainWindowMs)));
+ }
+ bgPolicy.handleUidBatteryUsage(uid, percentage);
+ }
+ // For debugging only.
+ for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) {
+ bgPolicy.handleUidBatteryUsage(mDebugUidPercentages.keyAt(i),
+ mDebugUidPercentages.valueAt(i));
+ }
+ } finally {
+ scheduleBatteryUsageStatsUpdateIfNecessary(mBatteryUsageStatsPollingIntervalMs);
+ }
+ }
+
+ /**
+ * Update the battery usage stats data, if it's allowed to do so.
+ *
+ * @return {@code true} if the battery stats is up to date.
+ */
+ private boolean updateBatteryUsageStatsIfNecessary(long now, boolean forceUpdate) {
+ boolean needUpdate = false;
+ boolean updated = false;
+ synchronized (mLock) {
+ if (mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs < now
+ || forceUpdate) {
+ // The data we have is outdated.
+ if (mBatteryUsageStatsUpdatePending) {
+ // An update is ongoing in parallel, just wait for it.
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ }
+ } else {
+ mBatteryUsageStatsUpdatePending = true;
+ needUpdate = true;
+ }
+ updated = true;
+ } else {
+ // The data is still fresh, no need to update it.
+ return false;
+ }
+ }
+ if (needUpdate) {
+ // We don't want to query the battery usage stats with mLock held.
+ updateBatteryUsageStatsOnce(now);
+ synchronized (mLock) {
+ mLastBatteryUsageSamplingTs = now;
+ mBatteryUsageStatsUpdatePending = false;
+ mLock.notifyAll();
+ }
+ }
+ return updated;
+ }
+
+ private void updateBatteryUsageStatsOnce(long now) {
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ final ArraySet<UserHandle> userIds = mTmpUserIds;
+ final SparseDoubleArray buf = mTmpUidBatteryUsage;
+ final BatteryStatsInternal batteryStatsInternal = mInjector.getBatteryStatsInternal();
+ final long windowSize = Math.min(now - mBootTimestamp, bgPolicy.mBgCurrentDrainWindowMs);
+
+ buf.clear();
+ userIds.clear();
+ synchronized (mLock) {
+ for (int i = mActiveUserIdStates.size() - 1; i >= 0; i--) {
+ userIds.add(UserHandle.of(mActiveUserIdStates.keyAt(i)));
+ if (!mActiveUserIdStates.valueAt(i)) {
+ mActiveUserIdStates.removeAt(i);
+ }
+ }
+ }
+
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, "updateBatteryUsageStatsOnce");
+ }
+
+ // Query the current battery usage stats.
+ BatteryUsageStatsQuery.Builder builder = new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .setMaxStatsAgeMs(0);
+ final BatteryUsageStats stats = updateBatteryUsageStatsOnceInternal(
+ buf, builder, userIds, batteryStatsInternal);
+ final long curStart = stats != null ? stats.getStatsStartTimestamp() : 0L;
+ long curDuration = now - curStart;
+ boolean needUpdateUidBatteryUsageInWindow = true;
+
+ if (curDuration >= windowSize) {
+ // If we do have long enough data for the window, save it.
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ needUpdateUidBatteryUsageInWindow = false;
+ }
+
+ // Save the current data, which includes the battery usage since last snapshot.
+ mTmpUidBatteryUsage2.clear();
+ copyUidBatteryUsage(buf, mTmpUidBatteryUsage2);
+
+ final long lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs;
+ mLastUidBatteryUsageStartTs = curStart;
+ if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) {
+ // The battery usage stats committed data since our last query,
+ // let's query the snapshots to get the data since last start.
+ builder = new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .aggregateSnapshots(lastUidBatteryUsageStartTs, curStart);
+ updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
+ curDuration += curStart - lastUidBatteryUsageStartTs;
+ }
+ if (needUpdateUidBatteryUsageInWindow && curDuration > windowSize) {
+ // If we do have long enough data for the window, save it.
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ needUpdateUidBatteryUsageInWindow = false;
+ }
+
+ // Add the delta into the global records.
+ for (int i = 0, size = buf.size(); i < size; i++) {
+ final int uid = buf.keyAt(i);
+ final int index = mUidBatteryUsage.indexOfKey(uid);
+ final double delta = Math.max(0.0d,
+ buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d));
+ final double before;
+ if (index >= 0) {
+ before = mUidBatteryUsage.valueAt(index);
+ mUidBatteryUsage.setValueAt(index, before + delta);
+ } else {
+ before = 0.0d;
+ mUidBatteryUsage.put(uid, delta);
+ }
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d);
+ String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before
+ + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + ", delta=" + actualDelta
+ + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d)
+ + ", curStart=" + curStart
+ + ", lastLastStart=" + lastUidBatteryUsageStartTs
+ + ", thisLastStart=" + mLastUidBatteryUsageStartTs;
+ if (actualDelta < 0.0d) {
+ // Something is wrong, the battery usage shouldn't be negative.
+ Slog.e(TAG, msg);
+ } else {
+ Slog.i(TAG, msg);
+ }
+ }
+ }
+ // Now update the mLastUidBatteryUsage with the data we just saved above.
+ copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage);
+ mTmpUidBatteryUsage2.clear();
+
+ if (needUpdateUidBatteryUsageInWindow) {
+ // No sufficient data for the full window still, query snapshots again.
+ builder = new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .aggregateSnapshots(now - windowSize, lastUidBatteryUsageStartTs);
+ updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal);
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow);
+ }
+ }
+
+ private static BatteryUsageStats updateBatteryUsageStatsOnceInternal(
+ SparseDoubleArray buf, BatteryUsageStatsQuery.Builder builder,
+ ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) {
+ for (int i = 0, size = userIds.size(); i < size; i++) {
+ builder.addUser(userIds.valueAt(i));
+ }
+ final List<BatteryUsageStats> statsList = batteryStatsInternal
+ .getBatteryUsageStats(Arrays.asList(builder.build()));
+ if (ArrayUtils.isEmpty(statsList)) {
+ // Shouldn't happen unless in test.
+ return null;
+ }
+ final BatteryUsageStats stats = statsList.get(0);
+ final List<UidBatteryConsumer> uidConsumers = stats.getUidBatteryConsumers();
+ if (uidConsumers != null) {
+ for (UidBatteryConsumer uidConsumer : uidConsumers) {
+ // TODO: b/200326767 - as we are not supporting per proc state attribution yet,
+ // we couldn't distinguish between a real FGS vs. a bound FGS proc state.
+ final int uid = uidConsumer.getUid();
+ final double bgUsage = getBgUsage(uidConsumer);
+ int index = buf.indexOfKey(uid);
+ if (index < 0) {
+ buf.put(uid, bgUsage);
+ } else {
+ buf.setValueAt(index, buf.valueAt(index) + bgUsage);
+ }
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, "updateBatteryUsageStatsOnceInternal uid=" + uid
+ + ", bgUsage=" + bgUsage
+ + ", start=" + stats.getStatsStartTimestamp()
+ + ", end=" + stats.getStatsEndTimestamp());
+ }
+ }
+ }
+ return stats;
+ }
+
+ private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest) {
+ dest.clear();
+ for (int i = source.size() - 1; i >= 0; i--) {
+ dest.put(source.keyAt(i), source.valueAt(i));
+ }
+ }
+
+ private static void copyUidBatteryUsage(SparseDoubleArray source, SparseDoubleArray dest,
+ double scale) {
+ dest.clear();
+ for (int i = source.size() - 1; i >= 0; i--) {
+ dest.put(source.keyAt(i), source.valueAt(i) * scale);
+ }
+ }
+
+ private static double getBgUsage(final UidBatteryConsumer uidConsumer) {
+ return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
+ + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+ }
+
+ private static double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+ final BatteryConsumer.Dimensions dimens) {
+ try {
+ return uidConsumer.getConsumedPower(dimens);
+ } catch (IllegalArgumentException e) {
+ return 0.0d;
+ }
+ }
+
+ private void onCurrentDrainMonitorEnabled(boolean enabled) {
+ if (enabled) {
+ if (!mBgHandler.hasCallbacks(mBgBatteryUsageStatsPolling)) {
+ mBgHandler.postDelayed(mBgBatteryUsageStatsPolling,
+ mBatteryUsageStatsPollingIntervalMs);
+ }
+ } else {
+ mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+ synchronized (mLock) {
+ if (mBatteryUsageStatsUpdatePending) {
+ // An update is ongoing in parallel, just wait for it.
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ mUidBatteryUsage.clear();
+ mUidBatteryUsageInWindow.clear();
+ mLastUidBatteryUsage.clear();
+ mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ synchronized (mLock) {
+ mUidBatteryUsage.clear();
+ mUidBatteryUsageInWindow.clear();
+ mLastUidBatteryUsage.clear();
+ mLastUidBatteryUsageStartTs = mLastBatteryUsageSamplingTs = 0L;
+ }
+ mBgHandler.removeCallbacks(mBgBatteryUsageStatsPolling);
+ updateBatteryUsageStatsAndCheck();
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BATTERY STATE TRACKER:");
+ updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+ final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow;
+ pw.print(" " + prefix);
+ pw.print("Boot=");
+ TimeUtils.dumpTime(pw, mBootTimestamp);
+ pw.print(" Last battery usage start=");
+ TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs);
+ pw.println();
+ pw.print(" " + prefix);
+ pw.print("Battery usage over last ");
+ final String newPrefix = " " + prefix;
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ final long now = SystemClock.elapsedRealtime();
+ final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs);
+ pw.println(TimeUtils.formatDuration(now - since));
+ if (uidConsumers.size() == 0) {
+ pw.print(newPrefix);
+ pw.println("(none)");
+ } else {
+ for (int i = 0, size = uidConsumers.size(); i < size; i++) {
+ final int uid = uidConsumers.keyAt(i);
+ final double bgUsage = uidConsumers.valueAt(i);
+ final double exemptedUsage = mAppRestrictionController
+ .getUidBatteryExemptedUsageSince(uid, since, now);
+ final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage);
+ pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | "
+ + "%.3f mAh (%4.2f%%) | %.3f mAh\n",
+ newPrefix, UserHandle.formatUid(uid),
+ PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)),
+ bgUsage , bgPolicy.getPercentage(uid, bgUsage),
+ exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage),
+ reportedUsage, bgPolicy.getPercentage(-1, reportedUsage),
+ mUidBatteryUsage.get(uid, 0.0d));
+ }
+ }
+ super.dump(pw, prefix);
+ }
+
+ static final class AppBatteryPolicy extends BaseAppStatePolicy<AppBatteryTracker> {
+ /**
+ * Whether or not we should enable the monitoring on background current drains.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_monitor_enabled";
+
+ /**
+ * The threshold of the background current drain (in percentage) to the restricted
+ * standby bucket. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
+ * the app could be moved to more restricted standby bucket when its background current
+ * drain rate is over this limit.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_restricted_bucket";
+
+ /**
+ * The threshold of the background current drain (in percentage) to the background
+ * restricted level. In conjunction with the {@link #KEY_BG_CURRENT_DRAIN_WINDOW},
+ * the app could be moved to more restricted level when its background current
+ * drain rate is over this limit.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_threshold_to_bg_restricted";
+
+ /**
+ * The background current drain window size. In conjunction with the
+ * {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, the app could be moved to
+ * more restrictive bucket when its background current drain rate is over this limit.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_WINDOW =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window";
+
+ /**
+ * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher
+ * value for the legitimate cases with higher background current drain.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+ + "current_drain_high_threshold_to_restricted_bucket";
+
+ /**
+ * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED}, but a higher value
+ * for the legitimate cases with higher background current drain.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_to_bg_restricted";
+
+ /**
+ * The threshold of minimal time of hosting a foreground service with type "mediaPlayback"
+ * or a media session, over the given window, so it'd subject towards the higher
+ * background current drain threshold as defined in
+ * {@link #mBgCurrentDrainBgRestrictedHighThreshold}.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_media_playback_min_duration";
+
+ /**
+ * Similar to {@link #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION} but for foreground
+ * service with type "location".
+ */
+ static final String KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_location_min_duration";
+
+ /**
+ * Whether or not we should enable the different threshold based on the durations of
+ * certain event type.
+ */
+ static final String KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX
+ + "current_drain_event_duration_based_threshold_enabled";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED = true;
+
+ /**
+ * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
+ * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
+ */
+ static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD =
+ isLowRamDeviceStatic() ? 4.0f : 2.0f;
+
+ /**
+ * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of
+ * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
+ */
+ static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD =
+ isLowRamDeviceStatic() ? 8.0f : 4.0f;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainWindowMs}.
+ */
+ static final long DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS = ONE_DAY;
+
+ /**
+ * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
+ * the {@link #mBgCurrentDrainRestrictedBucketThreshold}.
+ */
+ static final float DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD =
+ isLowRamDeviceStatic() ? 60.0f : 30.0f;
+
+ /**
+ * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of
+ * the {@link #mBgCurrentDrainBgRestrictedThreshold}.
+ */
+ static final float DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD =
+ isLowRamDeviceStatic() ? 60.0f : 30.0f;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainMediaPlaybackMinDuration}.
+ */
+ static final long DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION = 30 * ONE_MINUTE;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainLocationMinDuration}.
+ */
+ static final long DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION = 30 * ONE_MINUTE;
+
+ /**
+ * Default value to {@link #mBgCurrentDrainEventDurationBasedThresholdEnabled}.
+ */
+ static final boolean DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED =
+ false;
+
+ /**
+ * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold}
+ * and {@link #mBgCurrentDrainBgRestrictedThreshold}.
+ */
+ static final int INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD = 0;
+ static final int INDEX_HIGH_CURRENT_DRAIN_THRESHOLD = 1;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET.
+ * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET.
+ */
+ volatile float[] mBgCurrentDrainRestrictedBucketThreshold = {
+ DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD,
+ DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
+ };
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED.
+ * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED.
+ */
+ volatile float[] mBgCurrentDrainBgRestrictedThreshold = {
+ DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD,
+ DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD,
+ };
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_WINDOW.
+ */
+ volatile long mBgCurrentDrainWindowMs = DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION.
+ */
+ volatile long mBgCurrentDrainMediaPlaybackMinDuration =
+ DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION.
+ */
+ volatile long mBgCurrentDrainLocationMinDuration =
+ DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION;
+
+ /**
+ * @see #KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED.
+ */
+ volatile boolean mBgCurrentDrainEventDurationBasedThresholdEnabled;
+
+ /**
+ * The capacity of the battery when fully charged in mAh.
+ */
+ private int mBatteryFullChargeMah;
+
+ /**
+ * List of the packages with significant background battery usage, key is the UID of
+ * the package and value is an array of the timestamps when the UID is found guilty and
+ * should be moved to the next level of restriction.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<long[]> mHighBgBatteryPackages = new SparseArray<>();
+
+ @NonNull
+ private final Object mLock;
+
+ private static final int TIME_STAMP_INDEX_RESTRICTED_BUCKET = 0;
+ private static final int TIME_STAMP_INDEX_BG_RESTRICTED = 1;
+ private static final int TIME_STAMP_INDEX_LAST = 2;
+
+ AppBatteryPolicy(@NonNull Injector injector, @NonNull AppBatteryTracker tracker) {
+ super(injector, tracker, KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+ DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ mLock = tracker.mLock;
+ }
+
+ @Override
+ public void onPropertiesChanged(String name) {
+ switch (name) {
+ case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET:
+ case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED:
+ case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET:
+ case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED:
+ updateCurrentDrainThreshold();
+ break;
+ case KEY_BG_CURRENT_DRAIN_WINDOW:
+ updateCurrentDrainWindow();
+ break;
+ case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION:
+ updateCurrentDrainMediaPlaybackMinDuration();
+ break;
+ case KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION:
+ updateCurrentDrainLocationMinDuration();
+ break;
+ case KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED:
+ updateCurrentDrainEventDurationBasedThresholdEnabled();
+ break;
+ default:
+ super.onPropertiesChanged(name);
+ break;
+ }
+ }
+
+ void updateTrackerEnabled() {
+ if (mBatteryFullChargeMah > 0) {
+ super.updateTrackerEnabled();
+ } else {
+ mTrackerEnabled = false;
+ onTrackerEnabled(false);
+ }
+ }
+
+ public void onTrackerEnabled(boolean enabled) {
+ mTracker.onCurrentDrainMonitorEnabled(enabled);
+ }
+
+ private void updateCurrentDrainThreshold() {
+ mBgCurrentDrainRestrictedBucketThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
+ DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+ DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_THRESHOLD);
+ mBgCurrentDrainRestrictedBucketThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
+ DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
+ DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+ mBgCurrentDrainBgRestrictedThreshold[INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD] =
+ DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+ DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ mBgCurrentDrainBgRestrictedThreshold[INDEX_HIGH_CURRENT_DRAIN_THRESHOLD] =
+ DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
+ DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+ }
+
+ private void updateCurrentDrainWindow() {
+ mBgCurrentDrainWindowMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_WINDOW,
+ mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS
+ ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ }
+
+ private void updateCurrentDrainMediaPlaybackMinDuration() {
+ mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
+ DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+ }
+
+ private void updateCurrentDrainLocationMinDuration() {
+ mBgCurrentDrainLocationMinDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
+ DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+ }
+
+ private void updateCurrentDrainEventDurationBasedThresholdEnabled() {
+ mBgCurrentDrainEventDurationBasedThresholdEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
+ DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+ }
+
+ @Override
+ public void onSystemReady() {
+ mBatteryFullChargeMah =
+ mInjector.getBatteryManagerInternal().getBatteryFullCharge() / 1000;
+ super.onSystemReady();
+ updateCurrentDrainThreshold();
+ updateCurrentDrainWindow();
+ updateCurrentDrainMediaPlaybackMinDuration();
+ updateCurrentDrainLocationMinDuration();
+ updateCurrentDrainEventDurationBasedThresholdEnabled();
+ }
+
+ @Override
+ public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+ synchronized (mLock) {
+ final int index = mHighBgBatteryPackages.indexOfKey(uid);
+ if (index < 0) {
+ // Not found, return adaptive as the default one.
+ return RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+ }
+ final long[] ts = mHighBgBatteryPackages.valueAt(index);
+ return ts[TIME_STAMP_INDEX_BG_RESTRICTED] > 0
+ ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED
+ : RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+ }
+ }
+
+ double getBgUsage(final UidBatteryConsumer uidConsumer) {
+ return getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_BG)
+ + getConsumedPowerNoThrow(uidConsumer, BATT_DIMEN_FGS);
+ }
+
+ double getPercentage(final int uid, final double usage) {
+ final double actualPercentage = usage / mBatteryFullChargeMah * 100;
+ return DEBUG_BACKGROUND_BATTERY_TRACKER
+ ? mTracker.mDebugUidPercentages.get(uid, actualPercentage) : actualPercentage;
+ }
+
+ void handleUidBatteryUsage(final int uid, final double percentage) {
+ final @ReasonCode int reason = shouldExemptUid(uid);
+ if (reason != REASON_DENIED) {
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, "Exempting battery usage in " + UserHandle.formatUid(uid)
+ + " " + PowerExemptionManager.reasonCodeToString(reason));
+ }
+ return;
+ }
+ boolean notifyController = false;
+ boolean excessive = false;
+ synchronized (mLock) {
+ final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(uid);
+ if (curLevel >= RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ // We're already in the background restricted level, nothing more we could do.
+ return;
+ }
+ final long now = SystemClock.elapsedRealtime();
+ final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
+ mBgCurrentDrainWindowMs);
+ final int index = mHighBgBatteryPackages.indexOfKey(uid);
+ if (index < 0) {
+ if (percentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+ // New findings to us, track it and let the controller know.
+ final long[] ts = new long[TIME_STAMP_INDEX_LAST];
+ ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now;
+ mHighBgBatteryPackages.put(uid, ts);
+ notifyController = excessive = true;
+ }
+ } else {
+ final long[] ts = mHighBgBatteryPackages.valueAt(index);
+ if (percentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) {
+ // it's actually back to normal, but we don't untrack it until
+ // explicit user interactions.
+ notifyController = true;
+ } else {
+ excessive = true;
+ if (percentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
+ // If we're in the restricted standby bucket but still seeing high
+ // current drains, tell the controller again.
+ if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
+ && ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) {
+ if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
+ + mBgCurrentDrainWindowMs) {
+ ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
+ notifyController = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (excessive) {
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, "Excessive background current drain " + uid
+ + String.format(" %.2f%%", percentage) + " over "
+ + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
+ }
+ if (notifyController) {
+ mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(
+ uid, REASON_MAIN_FORCED_BY_SYSTEM,
+ REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
+ }
+ } else {
+ if (DEBUG_BACKGROUND_BATTERY_TRACKER) {
+ Slog.i(TAG, "Background current drain backs to normal " + uid
+ + String.format(" %.2f%%", percentage) + " over "
+ + TimeUtils.formatDuration(mBgCurrentDrainWindowMs));
+ }
+ // For now, we're not lifting the restrictions if the bg current drain backs to
+ // normal util an explicit user interaction.
+ }
+ }
+
+ private int getCurrentDrainThresholdIndex(int uid, long now, long window) {
+ return (hasMediaPlayback(uid, now, window) || hasLocation(uid, now, window))
+ ? INDEX_HIGH_CURRENT_DRAIN_THRESHOLD
+ : INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD;
+ }
+
+ private boolean hasMediaPlayback(int uid, long now, long window) {
+ return mBgCurrentDrainEventDurationBasedThresholdEnabled
+ && mTracker.mAppRestrictionController.getCompositeMediaPlaybackDurations(
+ uid, now, window) >= mBgCurrentDrainMediaPlaybackMinDuration;
+ }
+
+ private boolean hasLocation(int uid, long now, long window) {
+ final AppRestrictionController controller = mTracker.mAppRestrictionController;
+ if (mInjector.getPermissionManagerServiceInternal().checkUidPermission(
+ uid, ACCESS_BACKGROUND_LOCATION) == PERMISSION_GRANTED) {
+ return true;
+ }
+ if (!mBgCurrentDrainEventDurationBasedThresholdEnabled) {
+ return false;
+ }
+ final long since = Math.max(0, now - window);
+ final long locationDuration = controller.getForegroundServiceTotalDurationsSince(
+ uid, since, now, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+ return locationDuration >= mBgCurrentDrainLocationMinDuration;
+ }
+
+ void onUserInteractionStarted(String packageName, int uid) {
+ boolean changed = false;
+ synchronized (mLock) {
+ final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel(
+ uid, packageName);
+ if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ // It's a sticky state, user interaction won't change it, still track it.
+ } else {
+ // Remove the given UID from our tracking list, as user interacted with it.
+ final int index = mHighBgBatteryPackages.indexOfKey(uid);
+ if (index >= 0) {
+ mHighBgBatteryPackages.removeAt(index);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ // Request to refresh the app restriction level.
+ mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(uid,
+ REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION, true);
+ }
+ }
+
+ void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+ if (restricted) {
+ return;
+ }
+ synchronized (mLock) {
+ // User has explicitly removed it from background restricted level,
+ // clear the timestamp of the background-restricted
+ final long[] ts = mHighBgBatteryPackages.get(uid);
+ if (ts != null) {
+ ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0;
+ }
+ }
+ }
+
+ private double getConsumedPowerNoThrow(final UidBatteryConsumer uidConsumer,
+ final BatteryConsumer.Dimensions dimens) {
+ try {
+ return uidConsumer.getConsumedPower(dimens);
+ } catch (IllegalArgumentException e) {
+ return 0.0d;
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ mHighBgBatteryPackages.clear();
+ mTracker.reset();
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BATTERY TRACKER POLICY SETTINGS:");
+ final String indent = " ";
+ prefix = indent + prefix;
+ super.dump(pw, prefix);
+ if (isEnabled()) {
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET);
+ pw.print('=');
+ pw.println(mBgCurrentDrainRestrictedBucketThreshold[
+ INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET);
+ pw.print('=');
+ pw.println(mBgCurrentDrainRestrictedBucketThreshold[
+ INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED);
+ pw.print('=');
+ pw.println(mBgCurrentDrainBgRestrictedThreshold[
+ INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD]);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED);
+ pw.print('=');
+ pw.println(mBgCurrentDrainBgRestrictedThreshold[
+ INDEX_HIGH_CURRENT_DRAIN_THRESHOLD]);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_WINDOW);
+ pw.print('=');
+ pw.println(mBgCurrentDrainWindowMs);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+ pw.print('=');
+ pw.println(mBgCurrentDrainMediaPlaybackMinDuration);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+ pw.print('=');
+ pw.println(mBgCurrentDrainLocationMinDuration);
+ pw.print(prefix);
+ pw.print(KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+ pw.print('=');
+ pw.println(mBgCurrentDrainEventDurationBasedThresholdEnabled);
+
+ pw.print(prefix);
+ pw.println("Excessive current drain detected:");
+ synchronized (mLock) {
+ final int size = mHighBgBatteryPackages.size();
+ prefix = indent + prefix;
+ if (size > 0) {
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ final int uid = mHighBgBatteryPackages.keyAt(i);
+ final long[] ts = mHighBgBatteryPackages.valueAt(i);
+ final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now,
+ mBgCurrentDrainWindowMs);
+ pw.format("%s%s: (threshold=%4.2f%%/%4.2f%%) %s/%s\n",
+ prefix,
+ UserHandle.formatUid(uid),
+ mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex],
+ mBgCurrentDrainBgRestrictedThreshold[thresholdIndex],
+ ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] == 0 ? "0"
+ : formatTime(ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET], now),
+ ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0 ? "0"
+ : formatTime(ts[TIME_STAMP_INDEX_BG_RESTRICTED], now));
+ }
+ } else {
+ pw.print(prefix);
+ pw.println("(none)");
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
new file mode 100644
index 0000000..9e3cae6
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBindServiceEventsTracker.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.content.Context;
+
+import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+final class AppBindServiceEventsTracker extends BaseAppStateTimeSlotEventsTracker
+ <AppBindServiceEventsPolicy, SimpleAppStateTimeslotEvents>
+ implements BindServiceEventListener {
+
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppBindServiceEventsTracker" : TAG_AM;
+
+ static final boolean DEBUG_APP_STATE_BIND_SERVICE_EVENT_TRACKER = false;
+
+ AppBindServiceEventsTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppBindServiceEventsTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppBindServiceEventsPolicy>> injector,
+ Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mInjector.setPolicy(new AppBindServiceEventsPolicy(mInjector, this));
+ }
+
+ @Override
+ public void onBindingService(String packageName, int uid) {
+ if (mInjector.getPolicy().isEnabled()) {
+ onNewEvent(packageName, uid);
+ }
+ }
+
+ @Override
+ void onSystemReady() {
+ super.onSystemReady();
+ mInjector.getActivityManagerInternal().addBindServiceEventListener(this);
+ }
+
+ @Override
+ public SimpleAppStateTimeslotEvents createAppStateEvents(int uid, String packageName) {
+ return new SimpleAppStateTimeslotEvents(uid, packageName,
+ mInjector.getPolicy().getTimeSlotSize(), TAG, mInjector.getPolicy());
+ }
+
+ @Override
+ public SimpleAppStateTimeslotEvents createAppStateEvents(SimpleAppStateTimeslotEvents other) {
+ return new SimpleAppStateTimeslotEvents(other);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BIND SERVICE EVENT TRACKER:");
+ super.dump(pw, " " + prefix);
+ }
+
+ static final class AppBindServiceEventsPolicy
+ extends BaseAppStateTimeSlotEventsPolicy<AppBindServiceEventsTracker> {
+ /**
+ * Whether or not we should enable the monitoring on abusive service bindings requests.
+ */
+ static final String KEY_BG_BIND_SVC_MONITOR_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "bind_svc_monitor_enabled";
+
+ /**
+ * The size of the sliding window in which the number of service binding requests is checked
+ * against the threshold.
+ */
+ static final String KEY_BG_BIND_SVC_WINDOW =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "bind_svc_window";
+
+ /**
+ * The threshold at where the number of service binding requests are considered as
+ * "excessive" within the given window.
+ */
+ static final String KEY_BG_EX_BIND_SVC_THRESHOLD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "ex_bind_svc_threshold";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_BIND_SVC_MONITOR_ENABLED = true;
+
+ /**
+ * Default value to {@link #mMaxTrackingDuration}.
+ */
+ static final long DEFAULT_BG_BIND_SVC_WINDOW = ONE_DAY;
+
+ /**
+ * Default value to {@link #mNumOfEventsThreshold}.
+ */
+ static final int DEFAULT_BG_EX_BIND_SVC_THRESHOLD = 10_000;
+
+ AppBindServiceEventsPolicy(@NonNull Injector injector,
+ @NonNull AppBindServiceEventsTracker tracker) {
+ super(injector, tracker,
+ KEY_BG_BIND_SVC_MONITOR_ENABLED, DEFAULT_BG_BIND_SVC_MONITOR_ENABLED,
+ KEY_BG_BIND_SVC_WINDOW, DEFAULT_BG_BIND_SVC_WINDOW,
+ KEY_BG_EX_BIND_SVC_THRESHOLD, DEFAULT_BG_EX_BIND_SVC_THRESHOLD);
+ }
+
+ @Override
+ String getEventName() {
+ return "bindservice";
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BIND SERVICE EVENT TRACKER POLICY SETTINGS:");
+ super.dump(pw, " " + prefix);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.java
new file mode 100644
index 0000000..cafae40
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBroadcastEventsTracker.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 com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.content.Context;
+
+import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+final class AppBroadcastEventsTracker extends BaseAppStateTimeSlotEventsTracker
+ <AppBroadcastEventsPolicy, SimpleAppStateTimeslotEvents> implements BroadcastEventListener {
+
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppBroadcastEventsTracker" : TAG_AM;
+
+ static final boolean DEBUG_APP_STATE_BROADCAST_EVENT_TRACKER = false;
+
+ AppBroadcastEventsTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppBroadcastEventsTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppBroadcastEventsPolicy>> injector,
+ Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mInjector.setPolicy(new AppBroadcastEventsPolicy(mInjector, this));
+ }
+
+ @Override
+ public void onSendingBroadcast(String packageName, int uid) {
+ if (mInjector.getPolicy().isEnabled()) {
+ onNewEvent(packageName, uid);
+ }
+ }
+
+ @Override
+ void onSystemReady() {
+ super.onSystemReady();
+ mInjector.getActivityManagerInternal().addBroadcastEventListener(this);
+ }
+
+ @Override
+ public SimpleAppStateTimeslotEvents createAppStateEvents(int uid, String packageName) {
+ return new SimpleAppStateTimeslotEvents(uid, packageName,
+ mInjector.getPolicy().getTimeSlotSize(), TAG, mInjector.getPolicy());
+ }
+
+ @Override
+ public SimpleAppStateTimeslotEvents createAppStateEvents(SimpleAppStateTimeslotEvents other) {
+ return new SimpleAppStateTimeslotEvents(other);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BROADCAST EVENT TRACKER:");
+ super.dump(pw, " " + prefix);
+ }
+
+ static final class AppBroadcastEventsPolicy
+ extends BaseAppStateTimeSlotEventsPolicy<AppBroadcastEventsTracker> {
+ /**
+ * Whether or not we should enable the monitoring on abusive broadcasts.
+ */
+ static final String KEY_BG_BROADCAST_MONITOR_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "broadcast_monitor_enabled";
+
+ /**
+ * The size of the sliding window in which the number of broadcasts is checked
+ * against the threshold.
+ */
+ static final String KEY_BG_BROADCAST_WINDOW =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "broadcast_window";
+
+ /**
+ * The threshold at where the number of broadcasts are considered as "excessive"
+ * within the given window.
+ */
+ static final String KEY_BG_EX_BROADCAST_THRESHOLD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "ex_broadcast_threshold";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_BROADCAST_MONITOR_ENABLED = true;
+
+ /**
+ * Default value to {@link #mMaxTrackingDuration}.
+ */
+ static final long DEFAULT_BG_BROADCAST_WINDOW = ONE_DAY;
+
+ /**
+ * Default value to {@link #mNumOfEventsThreshold}.
+ */
+ static final int DEFAULT_BG_EX_BROADCAST_THRESHOLD = 10_000;
+
+ AppBroadcastEventsPolicy(@NonNull Injector injector,
+ @NonNull AppBroadcastEventsTracker tracker) {
+ super(injector, tracker,
+ KEY_BG_BROADCAST_MONITOR_ENABLED, DEFAULT_BG_BROADCAST_MONITOR_ENABLED,
+ KEY_BG_BROADCAST_WINDOW, DEFAULT_BG_BROADCAST_WINDOW,
+ KEY_BG_EX_BROADCAST_THRESHOLD, DEFAULT_BG_EX_BROADCAST_THRESHOLD);
+ }
+
+ @Override
+ String getEventName() {
+ return "broadcast";
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP BROADCAST EVENT TRACKER POLICY SETTINGS:");
+ super.dump(pw, " " + prefix);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
new file mode 100644
index 0000000..9c775b3
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+import static android.content.pm.ServiceInfo.NUM_OF_FOREGROUND_SERVICE_TYPES;
+import static android.content.pm.ServiceInfo.foregroundServiceTypeToLabel;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+import static com.android.server.am.BaseAppStateTracker.ONE_HOUR;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
+import android.app.IProcessObserver;
+import android.content.Context;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.AppFGSTracker.AppFGSPolicy;
+import com.android.server.am.AppFGSTracker.PackageDurations;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+/**
+ * The tracker for monitoring abusive (long-running) FGS.
+ */
+final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, PackageDurations>
+ implements ForegroundServiceStateListener {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppFGSTracker" : TAG_AM;
+
+ static final boolean DEBUG_BACKGROUND_FGS_TRACKER = false;
+
+ private final MyHandler mHandler;
+
+ // Unlocked since it's only accessed in single thread.
+ private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>();
+
+ final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+ }
+
+ @Override
+ public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
+ final String packageName = mAppRestrictionController.getPackageName(pid);
+ if (packageName != null) {
+ mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_CHANGED,
+ uid, serviceTypes, packageName).sendToTarget();
+ }
+ }
+
+ @Override
+ public void onProcessDied(int pid, int uid) {
+ }
+ };
+
+ @Override
+ public void onForegroundServiceStateChanged(String packageName,
+ int uid, int pid, boolean started) {
+ mHandler.obtainMessage(started ? MyHandler.MSG_FOREGROUND_SERVICES_STARTED
+ : MyHandler.MSG_FOREGROUND_SERVICES_STOPPED, pid, uid, packageName).sendToTarget();
+ }
+
+ private static class MyHandler extends Handler {
+ static final int MSG_FOREGROUND_SERVICES_STARTED = 0;
+ static final int MSG_FOREGROUND_SERVICES_STOPPED = 1;
+ static final int MSG_FOREGROUND_SERVICES_CHANGED = 2;
+ static final int MSG_CHECK_LONG_RUNNING_FGS = 3;
+
+ private final AppFGSTracker mTracker;
+
+ MyHandler(AppFGSTracker tracker) {
+ super(tracker.mBgHandler.getLooper());
+ mTracker = tracker;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_FOREGROUND_SERVICES_STARTED:
+ mTracker.handleForegroundServicesChanged(
+ (String) msg.obj, msg.arg1, msg.arg2, true);
+ break;
+ case MSG_FOREGROUND_SERVICES_STOPPED:
+ mTracker.handleForegroundServicesChanged(
+ (String) msg.obj, msg.arg1, msg.arg2, false);
+ break;
+ case MSG_FOREGROUND_SERVICES_CHANGED:
+ mTracker.handleForegroundServicesChanged(
+ (String) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case MSG_CHECK_LONG_RUNNING_FGS:
+ mTracker.checkLongRunningFgs();
+ break;
+ }
+ }
+ }
+
+ AppFGSTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppFGSTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppFGSPolicy>> injector, Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mHandler = new MyHandler(this);
+ mInjector.setPolicy(new AppFGSPolicy(mInjector, this));
+ }
+
+ @Override
+ void onSystemReady() {
+ super.onSystemReady();
+ mInjector.getActivityManagerInternal().addForegroundServiceStateListener(this);
+ mInjector.getActivityManagerInternal().registerProcessObserver(mProcessObserver);
+ }
+
+ @VisibleForTesting
+ @Override
+ void reset() {
+ mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+ super.reset();
+ }
+
+ @Override
+ public PackageDurations createAppStateEvents(int uid, String packageName) {
+ return new PackageDurations(uid, packageName, mInjector.getPolicy(), this);
+ }
+
+ @Override
+ public PackageDurations createAppStateEvents(PackageDurations other) {
+ return new PackageDurations(other);
+ }
+
+ private void handleForegroundServicesChanged(String packageName, int pid, int uid,
+ boolean started) {
+ if (!mInjector.getPolicy().isEnabled()) {
+ return;
+ }
+ final long now = SystemClock.elapsedRealtime();
+ boolean longRunningFGSGone = false;
+ final int exemptReason = mInjector.getPolicy().shouldExemptUid(uid);
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, (started ? "Starting" : "Stopping") + " fgs in "
+ + packageName + "/" + UserHandle.formatUid(uid)
+ + " exemptReason=" + exemptReason);
+ }
+ synchronized (mLock) {
+ PackageDurations pkg = mPkgEvents.get(uid, packageName);
+ if (pkg == null) {
+ pkg = createAppStateEvents(uid, packageName);
+ mPkgEvents.put(uid, packageName, pkg);
+ }
+ final boolean wasLongRunning = pkg.isLongRunning();
+ pkg.addEvent(started, now);
+ longRunningFGSGone = wasLongRunning && !pkg.hasForegroundServices();
+ if (longRunningFGSGone) {
+ pkg.setIsLongRunning(false);
+ }
+ pkg.mExemptReason = exemptReason;
+ // Reschedule the checks.
+ scheduleDurationCheckLocked(now);
+ }
+ if (longRunningFGSGone) {
+ // The long-running FGS is gone, cancel the notification.
+ mInjector.getPolicy().onLongRunningFgsGone(packageName, uid);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleDurationCheckLocked(long now) {
+ // Look for the active FGS with longest running time till now.
+ final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+ long longest = -1;
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, PackageDurations> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final PackageDurations pkg = val.valueAt(j);
+ if (!pkg.hasForegroundServices() || pkg.isLongRunning()) {
+ // No FGS or it's a known long-running FGS, ignore it.
+ continue;
+ }
+ longest = Math.max(getTotalDurations(pkg, now), longest);
+ }
+ }
+ // Schedule a check in the future.
+ mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+ if (longest >= 0) {
+ // We'd add the "service start foreground timeout", as the apps are allowed
+ // to call startForeground() within that timeout after the FGS being started.
+ final long future = mInjector.getServiceStartForegroundTimeout()
+ + Math.max(0, mInjector.getPolicy().getFgsLongRunningThreshold() - longest);
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Scheduling a FGS duration check at "
+ + TimeUtils.formatDuration(future));
+ }
+ mHandler.sendEmptyMessageDelayed(MyHandler.MSG_CHECK_LONG_RUNNING_FGS, future);
+ } else if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Not scheduling FGS duration check");
+ }
+ }
+
+ private void checkLongRunningFgs() {
+ final AppFGSPolicy policy = mInjector.getPolicy();
+ final ArrayMap<PackageDurations, Long> pkgWithLongFgs = mTmpPkgDurations;
+ final long now = SystemClock.elapsedRealtime();
+ final long threshold = policy.getFgsLongRunningThreshold();
+ final long windowSize = policy.getFgsLongRunningWindowSize();
+ final long trimTo = Math.max(0, now - windowSize);
+
+ synchronized (mLock) {
+ final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, PackageDurations> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final PackageDurations pkg = val.valueAt(j);
+ if (pkg.hasForegroundServices() && !pkg.isLongRunning()) {
+ final long totalDuration = getTotalDurations(pkg, now);
+ if (totalDuration >= threshold) {
+ pkgWithLongFgs.put(pkg, totalDuration);
+ pkg.setIsLongRunning(true);
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, pkg.mPackageName
+ + "/" + UserHandle.formatUid(pkg.mUid)
+ + " has FGS running for "
+ + TimeUtils.formatDuration(totalDuration)
+ + " over " + TimeUtils.formatDuration(windowSize));
+ }
+ }
+ }
+ }
+ }
+ // Trim the duration list, we don't need to keep track of all old records.
+ trim(trimTo);
+ }
+
+ final int size = pkgWithLongFgs.size();
+ if (size > 0) {
+ // Sort it by the durations.
+ final Integer[] indices = new Integer[size];
+ for (int i = 0; i < size; i++) {
+ indices[i] = i;
+ }
+ Arrays.sort(indices, (a, b) -> Long.compare(
+ pkgWithLongFgs.valueAt(a), pkgWithLongFgs.valueAt(b)));
+ // Notify it in the order of from longest to shortest durations.
+ for (int i = size - 1; i >= 0; i--) {
+ final PackageDurations pkg = pkgWithLongFgs.keyAt(indices[i]);
+ policy.onLongRunningFgs(pkg.mPackageName, pkg.mUid, pkg.mExemptReason);
+ }
+ pkgWithLongFgs.clear();
+ }
+
+ synchronized (mLock) {
+ scheduleDurationCheckLocked(now);
+ }
+ }
+
+ private void handleForegroundServicesChanged(String packageName, int uid, int serviceTypes) {
+ if (!mInjector.getPolicy().isEnabled()) {
+ return;
+ }
+ final int exemptReason = mInjector.getPolicy().shouldExemptUid(uid);
+ final long now = SystemClock.elapsedRealtime();
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Updating fgs type for " + packageName + "/" + UserHandle.formatUid(uid)
+ + " to " + Integer.toHexString(serviceTypes)
+ + " exemptReason=" + exemptReason);
+ }
+ synchronized (mLock) {
+ PackageDurations pkg = mPkgEvents.get(uid, packageName);
+ if (pkg == null) {
+ pkg = new PackageDurations(uid, packageName, mInjector.getPolicy(), this);
+ mPkgEvents.put(uid, packageName, pkg);
+ }
+ pkg.setForegroundServiceType(serviceTypes, now);
+ pkg.mExemptReason = exemptReason;
+ }
+ }
+
+ private void onBgFgsMonitorEnabled(boolean enabled) {
+ if (enabled) {
+ synchronized (mLock) {
+ scheduleDurationCheckLocked(SystemClock.elapsedRealtime());
+ }
+ } else {
+ mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
+ synchronized (mLock) {
+ mPkgEvents.clear();
+ }
+ }
+ }
+
+ private void onBgFgsLongRunningThresholdChanged() {
+ synchronized (mLock) {
+ if (mInjector.getPolicy().isEnabled()) {
+ scheduleDurationCheckLocked(SystemClock.elapsedRealtime());
+ }
+ }
+ }
+
+ static int foregroundServiceTypeToIndex(@ForegroundServiceType int serviceType) {
+ return serviceType == FOREGROUND_SERVICE_TYPE_NONE ? 0
+ : Integer.numberOfTrailingZeros(serviceType) + 1;
+ }
+
+ static @ForegroundServiceType int indexToForegroundServiceType(int index) {
+ return index == PackageDurations.DEFAULT_INDEX
+ ? FOREGROUND_SERVICE_TYPE_NONE : (1 << (index - 1));
+ }
+
+ long getTotalDurations(PackageDurations pkg, long now) {
+ return getTotalDurations(pkg.mPackageName, pkg.mUid, now,
+ foregroundServiceTypeToIndex(FOREGROUND_SERVICE_TYPE_NONE));
+ }
+
+ boolean hasForegroundServices(String packageName, int uid) {
+ synchronized (mLock) {
+ final PackageDurations pkg = mPkgEvents.get(uid, packageName);
+ return pkg != null && pkg.hasForegroundServices();
+ }
+ }
+
+ boolean hasForegroundServices(int uid) {
+ synchronized (mLock) {
+ final SparseArray<ArrayMap<String, PackageDurations>> map = mPkgEvents.getMap();
+ final ArrayMap<String, PackageDurations> pkgs = map.get(uid);
+ if (pkgs != null) {
+ for (int i = pkgs.size() - 1; i >= 0; i--) {
+ final PackageDurations pkg = pkgs.valueAt(i);
+ if (pkg.hasForegroundServices()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP FOREGROUND SERVICE TRACKER:");
+ super.dump(pw, " " + prefix);
+ }
+
+ /**
+ * Tracks the durations with active FGS for a given package.
+ */
+ static class PackageDurations extends BaseAppStateDurations<BaseTimeEvent> {
+ private final AppFGSTracker mTracker;
+
+ /**
+ * Whether or not this package is considered as having long-running FGS.
+ */
+ private boolean mIsLongRunning;
+
+ /**
+ * The current foreground service types, should be a combination of the values in
+ * {@link android.content.pm.ServiceInfo.ForegroundServiceType}.
+ */
+ private int mForegroundServiceTypes;
+
+ /**
+ * The index to the duration list array, where it holds the overall FGS stats of this
+ * package.
+ */
+ static final int DEFAULT_INDEX = foregroundServiceTypeToIndex(FOREGROUND_SERVICE_TYPE_NONE);
+
+ PackageDurations(int uid, String packageName,
+ MaxTrackingDurationConfig maxTrackingDurationConfig, AppFGSTracker tracker) {
+ super(uid, packageName, NUM_OF_FOREGROUND_SERVICE_TYPES + 1, TAG,
+ maxTrackingDurationConfig);
+ mEvents[DEFAULT_INDEX] = new LinkedList<>();
+ mTracker = tracker;
+ }
+
+ PackageDurations(@NonNull PackageDurations other) {
+ super(other);
+ mIsLongRunning = other.mIsLongRunning;
+ mForegroundServiceTypes = other.mForegroundServiceTypes;
+ mTracker = other.mTracker;
+ }
+
+ /**
+ * Add a foreground service start/stop event.
+ */
+ void addEvent(boolean startFgs, long now) {
+ addEvent(startFgs, new BaseTimeEvent(now), DEFAULT_INDEX);
+ if (!startFgs && !hasForegroundServices()) {
+ mIsLongRunning = false;
+ }
+
+ if (!startFgs && mForegroundServiceTypes != FOREGROUND_SERVICE_TYPE_NONE) {
+ // Save the stop time per service type.
+ for (int i = 1; i < mEvents.length; i++) {
+ if (mEvents[i] == null) {
+ continue;
+ }
+ if (isActive(i)) {
+ mEvents[i].add(new BaseTimeEvent(now));
+ notifyListenersOnEventIfNecessary(false, now,
+ indexToForegroundServiceType(i));
+ }
+ }
+ mForegroundServiceTypes = FOREGROUND_SERVICE_TYPE_NONE;
+ }
+ }
+
+ /**
+ * Called on the service type changes via the {@link android.app.Service#startForeground}.
+ */
+ void setForegroundServiceType(int serviceTypes, long now) {
+ if (serviceTypes == mForegroundServiceTypes || !hasForegroundServices()) {
+ // Nothing to do.
+ return;
+ }
+ int changes = serviceTypes ^ mForegroundServiceTypes;
+ for (int serviceType = Integer.highestOneBit(changes); serviceType != 0;) {
+ final int i = foregroundServiceTypeToIndex(serviceType);
+ if ((serviceTypes & serviceType) != 0) {
+ // Start this type.
+ if (mEvents[i] == null) {
+ mEvents[i] = new LinkedList<>();
+ }
+ if (!isActive(i)) {
+ mEvents[i].add(new BaseTimeEvent(now));
+ notifyListenersOnEventIfNecessary(true, now, serviceType);
+ }
+ } else {
+ // Stop this type.
+ if (mEvents[i] != null && isActive(i)) {
+ mEvents[i].add(new BaseTimeEvent(now));
+ notifyListenersOnEventIfNecessary(false, now, serviceType);
+ }
+ }
+ changes &= ~serviceType;
+ serviceType = Integer.highestOneBit(changes);
+ }
+ mForegroundServiceTypes = serviceTypes;
+ }
+
+ private void notifyListenersOnEventIfNecessary(boolean start, long now,
+ @ForegroundServiceType int serviceType) {
+ int eventType;
+ switch (serviceType) {
+ case FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK:
+ eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_MEDIA_PLAYBACK;
+ break;
+ case FOREGROUND_SERVICE_TYPE_LOCATION:
+ eventType = BaseAppStateDurationsTracker.EVENT_TYPE_FGS_LOCATION;
+ break;
+ default:
+ return;
+ }
+ mTracker.notifyListenersOnEvent(mUid, mPackageName, start, now, eventType);
+ }
+
+ void setIsLongRunning(boolean isLongRunning) {
+ mIsLongRunning = isLongRunning;
+ }
+
+ boolean isLongRunning() {
+ return mIsLongRunning;
+ }
+
+ boolean hasForegroundServices() {
+ return isActive(DEFAULT_INDEX);
+ }
+
+ @Override
+ String formatEventTypeLabel(int index) {
+ if (index == DEFAULT_INDEX) {
+ return "Overall foreground services: ";
+ } else {
+ return foregroundServiceTypeToLabel(indexToForegroundServiceType(index)) + ": ";
+ }
+ }
+ }
+
+ static final class AppFGSPolicy extends BaseAppStateEventsPolicy<AppFGSTracker> {
+ /**
+ * Whether or not we should enable the monitoring on abusive FGS.
+ */
+ static final String KEY_BG_FGS_MONITOR_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_monitor_enabled";
+
+ /**
+ * The size of the sliding window in which the accumulated FGS durations are checked
+ * against the threshold.
+ */
+ static final String KEY_BG_FGS_LONG_RUNNING_WINDOW =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_long_running_window";
+
+ /**
+ * The threshold at where the accumulated FGS durations are considered as "long-running"
+ * within the given window.
+ */
+ static final String KEY_BG_FGS_LONG_RUNNING_THRESHOLD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_long_running_threshold";
+
+ /**
+ * If a package has run FGS with "mediaPlayback" over this threshold, it won't be considered
+ * as a long-running FGS.
+ */
+ static final String KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_media_playback_threshold";
+
+ /**
+ * If a package has run FGS with "location" over this threshold, it won't be considered
+ * as a long-running FGS.
+ */
+ static final String KEY_BG_FGS_LOCATION_THRESHOLD =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "fgs_location_threshold";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_FGS_MONITOR_ENABLED = true;
+
+ /**
+ * Default value to {@link #mMaxTrackingDuration}.
+ */
+ static final long DEFAULT_BG_FGS_LONG_RUNNING_WINDOW = ONE_DAY;
+
+ /**
+ * Default value to {@link #mBgFgsLongRunningThresholdMs}.
+ */
+ static final long DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD = 20 * ONE_HOUR;
+
+ /**
+ * Default value to {@link #mBgFgsMediaPlaybackThresholdMs}.
+ */
+ static final long DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD = 4 * ONE_HOUR;
+
+ /**
+ * Default value to {@link #mBgFgsLocationThresholdMs}.
+ */
+ static final long DEFAULT_BG_FGS_LOCATION_THRESHOLD = 4 * ONE_HOUR;
+
+ /**
+ * @see #KEY_BG_FGS_LONG_RUNNING_THRESHOLD.
+ */
+ private volatile long mBgFgsLongRunningThresholdMs = DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD;
+
+ /**
+ * @see #KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD.
+ */
+ private volatile long mBgFgsMediaPlaybackThresholdMs =
+ DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD;
+
+ /**
+ * @see #KEY_BG_FGS_LOCATION_THRESHOLD.
+ */
+ private volatile long mBgFgsLocationThresholdMs = DEFAULT_BG_FGS_LOCATION_THRESHOLD;
+
+ AppFGSPolicy(@NonNull Injector injector, @NonNull AppFGSTracker tracker) {
+ super(injector, tracker, KEY_BG_FGS_MONITOR_ENABLED, DEFAULT_BG_FGS_MONITOR_ENABLED,
+ KEY_BG_FGS_LONG_RUNNING_WINDOW, DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+ }
+
+ @Override
+ public void onSystemReady() {
+ super.onSystemReady();
+ updateBgFgsLongRunningThreshold();
+ updateBgFgsMediaPlaybackThreshold();
+ updateBgFgsLocationThreshold();
+ }
+
+ @Override
+ public void onPropertiesChanged(String name) {
+ switch (name) {
+ case KEY_BG_FGS_LONG_RUNNING_THRESHOLD:
+ updateBgFgsLongRunningThreshold();
+ break;
+ case KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD:
+ updateBgFgsMediaPlaybackThreshold();
+ break;
+ case KEY_BG_FGS_LOCATION_THRESHOLD:
+ updateBgFgsLocationThreshold();
+ break;
+ default:
+ super.onPropertiesChanged(name);
+ break;
+ }
+ }
+
+ @Override
+ public void onTrackerEnabled(boolean enabled) {
+ mTracker.onBgFgsMonitorEnabled(enabled);
+ }
+
+ @Override
+ public void onMaxTrackingDurationChanged(long maxDuration) {
+ mTracker.onBgFgsLongRunningThresholdChanged();
+ }
+
+ private void updateBgFgsLongRunningThreshold() {
+ final long threshold = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+ DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+ if (threshold != mBgFgsLongRunningThresholdMs) {
+ mBgFgsLongRunningThresholdMs = threshold;
+ mTracker.onBgFgsLongRunningThresholdChanged();
+ }
+ }
+
+ private void updateBgFgsMediaPlaybackThreshold() {
+ mBgFgsMediaPlaybackThresholdMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD,
+ DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+ }
+
+ private void updateBgFgsLocationThreshold() {
+ mBgFgsLocationThresholdMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_FGS_LOCATION_THRESHOLD,
+ DEFAULT_BG_FGS_LOCATION_THRESHOLD);
+ }
+
+ long getFgsLongRunningThreshold() {
+ return mBgFgsLongRunningThresholdMs;
+ }
+
+ long getFgsLongRunningWindowSize() {
+ return getMaxTrackingDuration();
+ }
+
+ long getFGSMediaPlaybackThreshold() {
+ return mBgFgsMediaPlaybackThresholdMs;
+ }
+
+ long getLocationFGSThreshold() {
+ return mBgFgsLocationThresholdMs;
+ }
+
+ void onLongRunningFgs(String packageName, int uid, @ReasonCode int exemptReason) {
+ if (exemptReason != REASON_DENIED) {
+ return;
+ }
+ final long now = SystemClock.elapsedRealtime();
+ final long window = getFgsLongRunningWindowSize();
+ final long since = Math.max(0, now - window);
+ if (shouldExemptMediaPlaybackFGS(packageName, uid, now, window)) {
+ return;
+ }
+ if (shouldExemptLocationFGS(packageName, uid, now, since)) {
+ return;
+ }
+ if (hasBackgroundLocationPermission(packageName, uid)) {
+ // This package has background location permission, ignore it.
+ return;
+ }
+ mTracker.mAppRestrictionController.postLongRunningFgsIfNecessary(packageName, uid);
+ }
+
+ boolean shouldExemptMediaPlaybackFGS(String packageName, int uid, long now, long window) {
+ final long mediaPlaybackMs = mTracker.mAppRestrictionController
+ .getCompositeMediaPlaybackDurations(packageName, uid, now, window);
+ if (mediaPlaybackMs > 0 && mediaPlaybackMs >= getFGSMediaPlaybackThreshold()) {
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Ignoring long-running FGS in " + packageName + "/"
+ + UserHandle.formatUid(uid) + " media playback for "
+ + TimeUtils.formatDuration(mediaPlaybackMs));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean shouldExemptLocationFGS(String packageName, int uid, long now, long since) {
+ final long locationMs = mTracker.mAppRestrictionController
+ .getForegroundServiceTotalDurationsSince(packageName, uid, since, now,
+ FOREGROUND_SERVICE_TYPE_LOCATION);
+ if (locationMs > 0 && locationMs >= getLocationFGSThreshold()) {
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Ignoring long-running FGS in " + packageName + "/"
+ + UserHandle.formatUid(uid) + " location for "
+ + TimeUtils.formatDuration(locationMs));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean hasBackgroundLocationPermission(String packageName, int uid) {
+ if (mInjector.getPermissionManagerServiceInternal().checkPermission(
+ packageName, ACCESS_BACKGROUND_LOCATION, UserHandle.getUserId(uid))
+ == PERMISSION_GRANTED) {
+ if (DEBUG_BACKGROUND_FGS_TRACKER) {
+ Slog.i(TAG, "Ignoring bg-location FGS in " + packageName + "/"
+ + UserHandle.formatUid(uid));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+ if (reason != REASON_DENIED) {
+ return super.getExemptionReasonString(packageName, uid, reason);
+ }
+ final long now = SystemClock.elapsedRealtime();
+ final long window = getFgsLongRunningWindowSize();
+ final long since = Math.max(0, now - getFgsLongRunningWindowSize());
+ return "{mediaPlayback=" + shouldExemptMediaPlaybackFGS(packageName, uid, now, window)
+ + ", location=" + shouldExemptLocationFGS(packageName, uid, now, since)
+ + ", bgLocationPerm=" + hasBackgroundLocationPermission(packageName, uid) + "}";
+ }
+
+ void onLongRunningFgsGone(String packageName, int uid) {
+ mTracker.mAppRestrictionController
+ .cancelLongRunningFGSNotificationIfNecessary(packageName, uid);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP FOREGROUND SERVICE TRACKER POLICY SETTINGS:");
+ final String indent = " ";
+ prefix = indent + prefix;
+ super.dump(pw, prefix);
+ if (isEnabled()) {
+ pw.print(prefix);
+ pw.print(KEY_BG_FGS_LONG_RUNNING_THRESHOLD);
+ pw.print('=');
+ pw.println(mBgFgsLongRunningThresholdMs);
+ pw.print(prefix);
+ pw.print(KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+ pw.print('=');
+ pw.println(mBgFgsMediaPlaybackThresholdMs);
+ pw.print(prefix);
+ pw.print(KEY_BG_FGS_LOCATION_THRESHOLD);
+ pw.print('=');
+ pw.println(mBgFgsLocationThresholdMs);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppMediaSessionTracker.java b/services/core/java/com/android/server/am/AppMediaSessionTracker.java
new file mode 100644
index 0000000..3914f6f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppMediaSessionTracker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX;
+import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_TYPE_MEDIA_SESSION;
+import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.os.HandlerExecutor;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.app.ProcessMap;
+import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.BaseAppStateDurationsTracker.SimplePackageDurations;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+/**
+ * The tracker for monitoring the active media sessions of apps.
+ */
+final class AppMediaSessionTracker
+ extends BaseAppStateDurationsTracker<AppMediaSessionPolicy, SimplePackageDurations> {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppMediaSessionTracker" : TAG_AM;
+
+ static final boolean DEBUG_MEDIA_SESSION_TRACKER = false;
+
+ private final HandlerExecutor mHandlerExecutor;
+ private final OnActiveSessionsChangedListener mSessionsChangedListener =
+ this::handleMediaSessionChanged;
+
+ // Unlocked since it's only accessed in single thread.
+ private final ProcessMap<Boolean> mTmpMediaControllers = new ProcessMap<>();
+
+ AppMediaSessionTracker(Context context, AppRestrictionController controller) {
+ this(context, controller, null, null);
+ }
+
+ AppMediaSessionTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<AppMediaSessionPolicy>> injector, Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mHandlerExecutor = new HandlerExecutor(mBgHandler);
+ mInjector.setPolicy(new AppMediaSessionPolicy(mInjector, this));
+ }
+
+ @Override
+ public SimplePackageDurations createAppStateEvents(int uid, String packageName) {
+ return new SimplePackageDurations(uid, packageName, mInjector.getPolicy());
+ }
+
+ @Override
+ public SimplePackageDurations createAppStateEvents(SimplePackageDurations other) {
+ return new SimplePackageDurations(other);
+ }
+
+ private void onBgMediaSessionMonitorEnabled(boolean enabled) {
+ if (enabled) {
+ mInjector.getMediaSessionManager().addOnActiveSessionsChangedListener(
+ null, UserHandle.ALL, mHandlerExecutor, mSessionsChangedListener);
+ } else {
+ mInjector.getMediaSessionManager().removeOnActiveSessionsChangedListener(
+ mSessionsChangedListener);
+ }
+ }
+
+ private void handleMediaSessionChanged(List<MediaController> controllers) {
+ if (controllers != null) {
+ synchronized (mLock) {
+ final long now = SystemClock.elapsedRealtime();
+ for (MediaController controller : controllers) {
+ final String packageName = controller.getPackageName();
+ final int uid = controller.getSessionToken().getUid();
+ SimplePackageDurations pkg = mPkgEvents.get(uid, packageName);
+ if (pkg == null) {
+ pkg = createAppStateEvents(uid, packageName);
+ mPkgEvents.put(uid, packageName, pkg);
+ }
+ if (!pkg.isActive()) {
+ pkg.addEvent(true, now);
+ notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, true, now,
+ EVENT_TYPE_MEDIA_SESSION);
+ }
+ // Mark it as active, so we could filter out inactive ones below.
+ mTmpMediaControllers.put(packageName, uid, Boolean.TRUE);
+
+ if (DEBUG_MEDIA_SESSION_TRACKER) {
+ Slog.i(TAG, "Active media session from " + packageName + "/"
+ + UserHandle.formatUid(uid));
+ }
+ }
+
+ // Iterate the duration list and stop those inactive ones.
+ final SparseArray<ArrayMap<String, SimplePackageDurations>> map =
+ mPkgEvents.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, SimplePackageDurations> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final SimplePackageDurations pkg = val.valueAt(j);
+ if (pkg.isActive()
+ && mTmpMediaControllers.get(pkg.mPackageName, pkg.mUid) == null) {
+ // This package has removed its controller, issue a stop event.
+ pkg.addEvent(false, now);
+ notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now,
+ EVENT_TYPE_MEDIA_SESSION);
+ }
+ }
+ }
+ }
+ mTmpMediaControllers.clear();
+ } else {
+ synchronized (mLock) {
+ // Issue stop event to all active trackers.
+ final SparseArray<ArrayMap<String, SimplePackageDurations>> map =
+ mPkgEvents.getMap();
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, SimplePackageDurations> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final SimplePackageDurations pkg = val.valueAt(j);
+ if (pkg.isActive()) {
+ pkg.addEvent(false, now);
+ notifyListenersOnEvent(pkg.mUid, pkg.mPackageName, false, now,
+ EVENT_TYPE_MEDIA_SESSION);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void trimDurations() {
+ final long now = SystemClock.elapsedRealtime();
+ trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP MEDIA SESSION TRACKER:");
+ super.dump(pw, " " + prefix);
+ }
+
+ static final class AppMediaSessionPolicy
+ extends BaseAppStateEventsPolicy<AppMediaSessionTracker> {
+ /**
+ * Whether or not we should enable the monitoring on media sessions.
+ */
+ static final String KEY_BG_MEADIA_SESSION_MONITOR_ENABLED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "media_session_monitor_enabled";
+
+ /**
+ * The maximum duration we'd keep tracking, events earlier than that will be discarded.
+ */
+ static final String KEY_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "media_session_monitor_max_tracking_duration";
+
+ /**
+ * Default value to {@link #mTrackerEnabled}.
+ */
+ static final boolean DEFAULT_BG_MEDIA_SESSION_MONITOR_ENABLED = true;
+
+ /**
+ * Default value to {@link #mBgMediaSessionMonitorMaxTrackingDurationMs}.
+ */
+ static final long DEFAULT_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION =
+ 4 * ONE_DAY;
+
+ AppMediaSessionPolicy(@NonNull Injector injector, @NonNull AppMediaSessionTracker tracker) {
+ super(injector, tracker,
+ KEY_BG_MEADIA_SESSION_MONITOR_ENABLED,
+ DEFAULT_BG_MEDIA_SESSION_MONITOR_ENABLED,
+ KEY_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION,
+ DEFAULT_BG_MEDIA_SESSION_MONITOR_MAX_TRACKING_DURATION);
+ }
+
+ @Override
+ public void onTrackerEnabled(boolean enabled) {
+ mTracker.onBgMediaSessionMonitorEnabled(enabled);
+ }
+
+ @Override
+ public void onMaxTrackingDurationChanged(long maxDuration) {
+ mTracker.mBgHandler.post(mTracker::trimDurations);
+ }
+
+ @Override
+ String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+ // This tracker is a helper class for other trackers, we don't track exemptions here.
+ return "n/a";
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("APP MEDIA SESSION TRACKER POLICY SETTINGS:");
+ super.dump(pw, " " + prefix);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
new file mode 100644
index 0000000..bd63a24
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -0,0 +1,2068 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
+import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
+import static android.app.ActivityManager.UID_OBSERVER_GONE;
+import static android.app.ActivityManager.UID_OBSERVER_IDLE;
+import static android.app.ActivityManager.UID_OBSERVER_PROCSTATE;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_UNDEFINED;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.app.usage.UsageStatsManager.reasonToString;
+import static android.content.Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
+import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
+import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
+import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
+import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
+import static android.os.PowerExemptionManager.REASON_ROLE_DIALER;
+import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.AppFGSTracker.foregroundServiceTypeToIndex;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.app.usage.AppStandbyInfo;
+import android.app.usage.UsageStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ServiceInfo;
+import android.content.pm.ServiceInfo.ForegroundServiceType;
+import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+import android.provider.DeviceConfig.Properties;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseArrayMap;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.function.TriConsumer;
+import com.android.server.AppStateTracker;
+import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.usage.AppStandbyInternal;
+import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.function.Consumer;
+
+/**
+ * This class tracks various state of the apps and mutates their restriction levels accordingly.
+ */
+public final class AppRestrictionController {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "AppRestrictionController" : TAG_AM;
+ static final boolean DEBUG_BG_RESTRICTION_CONTROLLER = false;
+
+ /**
+ * The prefix for the sub-namespace of our device configs under
+ * the {@link android.provider.DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}.
+ */
+ static final String DEVICE_CONFIG_SUBNAMESPACE_PREFIX = "bg_";
+
+ static final int STOCK_PM_FLAGS = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
+ | MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+
+ /**
+ * Whether or not to show the foreground service manager on tapping notifications.
+ */
+ private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = false;
+
+ private final Context mContext;
+ private final HandlerThread mBgHandlerThread;
+ private final BgHandler mBgHandler;
+ private final HandlerExecutor mBgExecutor;
+
+ // No lock is needed, as it's immutable after initialization in constructor.
+ private final ArrayList<BaseAppStateTracker> mAppStateTrackers = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final RestrictionSettings mRestrictionSettings = new RestrictionSettings();
+
+ private final CopyOnWriteArraySet<AppBackgroundRestrictionListener> mRestrictionListeners =
+ new CopyOnWriteArraySet<>();
+
+ /**
+ * A mapping between the UID/Pkg and its pending work which should be triggered on inactive;
+ * an active UID/pkg pair should have an entry here, although its pending work could be null.
+ */
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Runnable> mActiveUids = new SparseArrayMap<>();
+
+ // No lock is needed as it's accessed in bg handler thread only.
+ private final ArrayList<Runnable> mTmpRunnables = new ArrayList<>();
+
+ /**
+ * Power-save allowlisted app-ids (not including except-idle-allowlisted ones).
+ */
+ private int[] mDeviceIdleAllowlist = new int[0]; // No lock is needed.
+
+ /**
+ * Power-save allowlisted app-ids (including except-idle-allowlisted ones).
+ */
+ private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
+
+ private final Object mLock = new Object();
+ private final Injector mInjector;
+ private final NotificationHelper mNotificationHelper;
+
+ private final OnRoleHoldersChangedListener mRoleHolderChangedListener =
+ this::onRoleHoldersChanged;
+
+ /**
+ * The key is the UID, the value is the list of the roles it holds.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ArrayList<String>> mUidRolesMapping = new SparseArray<>();
+
+ /**
+ * Cache the package name and information about if it's a system module.
+ */
+ @GuardedBy("mLock")
+ private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>();
+
+ final ActivityManagerService mActivityManagerService;
+
+ /**
+ * The restriction levels that each package is on, the levels here are defined in
+ * {@link android.app.ActivityManager.RESTRICTION_LEVEL_*}.
+ */
+ final class RestrictionSettings {
+ @GuardedBy("mLock")
+ final SparseArrayMap<String, PkgSettings> mRestrictionLevels = new SparseArrayMap();
+
+ final class PkgSettings {
+ private final String mPackageName;
+ private final int mUid;
+
+ private @RestrictionLevel int mCurrentRestrictionLevel;
+ private @RestrictionLevel int mLastRestrictionLevel;
+ private @ElapsedRealtimeLong long mLevelChangeTimeElapsed;
+ private int mReason;
+
+ private @ElapsedRealtimeLong long[] mLastNotificationShownTimeElapsed;
+ private int[] mNotificationId;
+
+ PkgSettings(String packageName, int uid) {
+ mPackageName = packageName;
+ mUid = uid;
+ mCurrentRestrictionLevel = mLastRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
+ }
+
+ @RestrictionLevel int update(@RestrictionLevel int level, int reason, int subReason) {
+ if (level != mCurrentRestrictionLevel) {
+ mLastRestrictionLevel = mCurrentRestrictionLevel;
+ mCurrentRestrictionLevel = level;
+ mLevelChangeTimeElapsed = SystemClock.elapsedRealtime();
+ mReason = (REASON_MAIN_MASK & reason) | (REASON_SUB_MASK & subReason);
+ mBgHandler.obtainMessage(BgHandler.MSG_APP_RESTRICTION_LEVEL_CHANGED,
+ mUid, level, mPackageName).sendToTarget();
+ }
+ return mLastRestrictionLevel;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(128);
+ sb.append("RestrictionLevel{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(':');
+ sb.append(mPackageName);
+ sb.append('/');
+ sb.append(UserHandle.formatUid(mUid));
+ sb.append('}');
+ sb.append(' ');
+ sb.append(ActivityManager.restrictionLevelToName(mCurrentRestrictionLevel));
+ sb.append('(');
+ sb.append(reasonToString(mReason));
+ sb.append(')');
+ return sb.toString();
+ }
+
+ void dump(PrintWriter pw, @ElapsedRealtimeLong long nowElapsed) {
+ pw.print(toString());
+ if (mLastRestrictionLevel != RESTRICTION_LEVEL_UNKNOWN) {
+ pw.print('/');
+ pw.print(ActivityManager.restrictionLevelToName(mLastRestrictionLevel));
+ }
+ pw.print(" levelChange=");
+ TimeUtils.formatDuration(mLevelChangeTimeElapsed - nowElapsed, pw);
+ if (mLastNotificationShownTimeElapsed != null) {
+ for (int i = 0; i < mLastNotificationShownTimeElapsed.length; i++) {
+ if (mLastNotificationShownTimeElapsed[i] > 0) {
+ pw.print(" lastNoti(");
+ pw.print(mNotificationHelper.notificationTypeToString(i));
+ pw.print(")=");
+ TimeUtils.formatDuration(
+ mLastNotificationShownTimeElapsed[i] - nowElapsed, pw);
+ }
+ }
+ }
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ int getUid() {
+ return mUid;
+ }
+
+ @RestrictionLevel int getCurrentRestrictionLevel() {
+ return mCurrentRestrictionLevel;
+ }
+
+ @RestrictionLevel int getLastRestrictionLevel() {
+ return mLastRestrictionLevel;
+ }
+
+ int getReason() {
+ return mReason;
+ }
+
+ @ElapsedRealtimeLong long getLastNotificationTime(
+ @NotificationHelper.NotificationType int notificationType) {
+ if (mLastNotificationShownTimeElapsed == null) {
+ return 0;
+ }
+ return mLastNotificationShownTimeElapsed[notificationType];
+ }
+
+ void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
+ @ElapsedRealtimeLong long timestamp) {
+ if (mLastNotificationShownTimeElapsed == null) {
+ mLastNotificationShownTimeElapsed =
+ new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
+ }
+ mLastNotificationShownTimeElapsed[notificationType] = timestamp;
+ }
+
+ int getNotificationId(@NotificationHelper.NotificationType int notificationType) {
+ if (mNotificationId == null) {
+ return 0;
+ }
+ return mNotificationId[notificationType];
+ }
+
+ void setNotificationId(@NotificationHelper.NotificationType int notificationType,
+ int notificationId) {
+ if (mNotificationId == null) {
+ mNotificationId = new int[NotificationHelper.NOTIFICATION_TYPE_LAST];
+ }
+ mNotificationId[notificationType] = notificationId;
+ }
+ }
+
+ /**
+ * Update the restriction level.
+ *
+ * @return The previous restriction level.
+ */
+ @RestrictionLevel int update(String packageName, int uid, @RestrictionLevel int level,
+ int reason, int subReason) {
+ synchronized (mLock) {
+ PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
+ if (settings == null) {
+ settings = new PkgSettings(packageName, uid);
+ mRestrictionLevels.add(uid, packageName, settings);
+ }
+ return settings.update(level, reason, subReason);
+ }
+ }
+
+ /**
+ * @return The reason of why it's in this level.
+ */
+ int getReason(String packageName, int uid) {
+ synchronized (mLock) {
+ final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
+ return settings != null ? settings.getReason()
+ : (REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED);
+ }
+ }
+
+ @RestrictionLevel int getRestrictionLevel(int uid) {
+ synchronized (mLock) {
+ final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
+ if (uidKeyIndex < 0) {
+ return RESTRICTION_LEVEL_UNKNOWN;
+ }
+ final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
+ if (numPackages == 0) {
+ return RESTRICTION_LEVEL_UNKNOWN;
+ }
+ @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
+ for (int i = 0; i < numPackages; i++) {
+ final PkgSettings setting = mRestrictionLevels.valueAt(uidKeyIndex, i);
+ if (setting != null) {
+ final @RestrictionLevel int l = setting.getCurrentRestrictionLevel();
+ level = (level == RESTRICTION_LEVEL_UNKNOWN) ? l : Math.min(level, l);
+ }
+ }
+ return level;
+ }
+ }
+
+ @RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
+ synchronized (mLock) {
+ final PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
+ return settings == null
+ ? getRestrictionLevel(uid) : settings.getCurrentRestrictionLevel();
+ }
+ }
+
+ @RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+ final int uid = pm.getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+ return getRestrictionLevel(uid, packageName);
+ }
+
+ private @RestrictionLevel int getLastRestrictionLevel(int uid, String packageName) {
+ synchronized (mLock) {
+ final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
+ return settings == null
+ ? RESTRICTION_LEVEL_UNKNOWN : settings.mLastRestrictionLevel;
+ }
+ }
+
+ @GuardedBy("mLock")
+ void forEachPackageInUidLocked(int uid,
+ @NonNull TriConsumer<String, Integer, Integer> consumer) {
+ final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
+ if (uidKeyIndex < 0) {
+ return;
+ }
+ final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
+ for (int i = 0; i < numPackages; i++) {
+ final PkgSettings settings = mRestrictionLevels.valueAt(uidKeyIndex, i);
+ consumer.accept(mRestrictionLevels.keyAt(uidKeyIndex, i),
+ settings.getCurrentRestrictionLevel(), settings.getReason());
+ }
+ }
+
+ @GuardedBy("mLock")
+ void forEachUidLocked(@NonNull Consumer<Integer> consumer) {
+ for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
+ consumer.accept(mRestrictionLevels.keyAt(i));
+ }
+ }
+
+ @GuardedBy("mLock")
+ PkgSettings getRestrictionSettingsLocked(int uid, String packageName) {
+ return mRestrictionLevels.get(uid, packageName);
+ }
+
+ void removeUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
+ final int uid = mRestrictionLevels.keyAt(i);
+ if (UserHandle.getUserId(uid) != userId) {
+ continue;
+ }
+ mRestrictionLevels.deleteAt(i);
+ }
+ }
+ }
+
+ void removePackage(String pkgName, int uid) {
+ synchronized (mLock) {
+ mRestrictionLevels.delete(uid, pkgName);
+ }
+ }
+
+ void removeUid(int uid) {
+ synchronized (mLock) {
+ mRestrictionLevels.delete(uid);
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ synchronized (mLock) {
+ mRestrictionLevels.clear();
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dumpLocked(PrintWriter pw, String prefix) {
+ final ArrayList<PkgSettings> settings = new ArrayList<>();
+ mRestrictionLevels.forEach(setting -> settings.add(setting));
+ Collections.sort(settings, Comparator.comparingInt(PkgSettings::getUid));
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int i = 0, size = settings.size(); i < size; i++) {
+ pw.print(prefix);
+ pw.print('#');
+ pw.print(i);
+ pw.print(' ');
+ settings.get(i).dump(pw, nowElapsed);
+ pw.println();
+ }
+ }
+ }
+
+ final class ConstantsObserver extends ContentObserver implements
+ OnPropertiesChangedListener {
+ /**
+ * Whether or not to set the app to restricted standby bucket automatically
+ * when it's background-restricted.
+ */
+ static final String KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restricted_bucket_on_bg_restricted";
+
+ /**
+ * The minimal interval in ms before posting a notification again on abusive behaviors
+ * of a certain package.
+ */
+ static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
+
+ static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = true;
+ static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000;
+
+ volatile boolean mBgAutoRestrictedBucket;
+
+ volatile boolean mRestrictedBucketEnabled;
+
+ volatile long mBgNotificationMinIntervalMs;
+
+ ConstantsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (name == null || !name.startsWith(DEVICE_CONFIG_SUBNAMESPACE_PREFIX)) {
+ return;
+ }
+ switch (name) {
+ case KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION:
+ updateBgAutoRestrictedBucketChanged();
+ break;
+ case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL:
+ updateBgAbusiveNotificationMinimalInterval();
+ break;
+ }
+ AppRestrictionController.this.onPropertiesChanged(name);
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSettings();
+ }
+
+ public void start() {
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET),
+ false, this);
+ updateSettings();
+ updateDeviceConfig();
+ }
+
+ void updateSettings() {
+ mRestrictedBucketEnabled = isRestrictedBucketEnabled();
+ }
+
+ private boolean isRestrictedBucketEnabled() {
+ return Global.getInt(mContext.getContentResolver(),
+ Global.ENABLE_RESTRICTED_BUCKET,
+ Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1;
+ }
+
+ void updateDeviceConfig() {
+ updateBgAutoRestrictedBucketChanged();
+ updateBgAbusiveNotificationMinimalInterval();
+ }
+
+ private void updateBgAutoRestrictedBucketChanged() {
+ boolean oldValue = mBgAutoRestrictedBucket;
+ mBgAutoRestrictedBucket = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION,
+ DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
+ if (oldValue != mBgAutoRestrictedBucket) {
+ dispatchAutoRestrictedBucketFeatureFlagChanged(mBgAutoRestrictedBucket);
+ }
+ }
+
+ private void updateBgAbusiveNotificationMinimalInterval() {
+ mBgNotificationMinIntervalMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL,
+ DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
+ }
+ }
+
+ private final ConstantsObserver mConstantsObserver;
+
+ private final AppStateTracker.BackgroundRestrictedAppListener mBackgroundRestrictionListener =
+ new AppStateTracker.BackgroundRestrictedAppListener() {
+ @Override
+ public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
+ boolean restricted) {
+ mBgHandler.obtainMessage(BgHandler.MSG_BACKGROUND_RESTRICTION_CHANGED,
+ uid, restricted ? 1 : 0, packageName).sendToTarget();
+ }
+ };
+
+ private final AppIdleStateChangeListener mAppIdleStateChangeListener =
+ new AppIdleStateChangeListener() {
+ @Override
+ public void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
+ boolean idle, int bucket, int reason) {
+ mBgHandler.obtainMessage(BgHandler.MSG_APP_STANDBY_BUCKET_CHANGED,
+ userId, bucket, packageName).sendToTarget();
+ }
+
+ @Override
+ public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
+ mBgHandler.obtainMessage(BgHandler.MSG_USER_INTERACTION_STARTED,
+ userId, 0, packageName).sendToTarget();
+ }
+ };
+
+ private final IUidObserver mUidObserver =
+ new IUidObserver.Stub() {
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq,
+ int capability) {
+ mBgHandler.obtainMessage(BgHandler.MSG_UID_PROC_STATE_CHANGED, uid, procState)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {
+ mBgHandler.obtainMessage(BgHandler.MSG_UID_IDLE, uid, disabled ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ mBgHandler.obtainMessage(BgHandler.MSG_UID_GONE, uid, disabled ? 1 : 0)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onUidActive(int uid) {
+ mBgHandler.obtainMessage(BgHandler.MSG_UID_ACTIVE, uid, 0).sendToTarget();
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+
+ /**
+ * Register the background restriction listener callback.
+ */
+ public void addAppBackgroundRestrictionListener(
+ @NonNull AppBackgroundRestrictionListener listener) {
+ mRestrictionListeners.add(listener);
+ }
+
+ AppRestrictionController(final Context context, final ActivityManagerService service) {
+ this(new Injector(context), service);
+ }
+
+ AppRestrictionController(final Injector injector, final ActivityManagerService service) {
+ mInjector = injector;
+ mContext = injector.getContext();
+ mActivityManagerService = service;
+ mBgHandlerThread = new HandlerThread("bgres-controller");
+ mBgHandlerThread.start();
+ mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
+ mBgExecutor = new HandlerExecutor(mBgHandler);
+ mConstantsObserver = new ConstantsObserver(mBgHandler);
+ mNotificationHelper = new NotificationHelper(this);
+ injector.initAppStateTrackers(this);
+ }
+
+ void onSystemReady() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ActivityThread.currentApplication().getMainExecutor(), mConstantsObserver);
+ mConstantsObserver.start();
+ initRestrictionStates();
+ initSystemModuleNames();
+ registerForUidObservers();
+ registerForSystemBroadcasts();
+ mNotificationHelper.onSystemReady();
+ mInjector.getAppStateTracker().addBackgroundRestrictedAppListener(
+ mBackgroundRestrictionListener);
+ mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener);
+ mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor,
+ mRoleHolderChangedListener, UserHandle.ALL);
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onSystemReady();
+ }
+ }
+
+ @VisibleForTesting
+ void resetRestrictionSettings() {
+ mRestrictionSettings.reset();
+ initRestrictionStates();
+ }
+
+ private void initRestrictionStates() {
+ final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
+ for (int userId : allUsers) {
+ refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
+ REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+ }
+ }
+
+ private void initSystemModuleNames() {
+ final PackageManager pm = mInjector.getPackageManager();
+ final List<ModuleInfo> moduleInfos = pm.getInstalledModules(0 /* flags */);
+ if (moduleInfos == null) {
+ return;
+ }
+ synchronized (mLock) {
+ for (ModuleInfo info : moduleInfos) {
+ mSystemModulesCache.put(info.getPackageName(), Boolean.TRUE);
+ }
+ }
+ }
+
+ private boolean isSystemModule(String packageName) {
+ synchronized (mLock) {
+ final Boolean val = mSystemModulesCache.get(packageName);
+ if (val != null) {
+ return val.booleanValue();
+ }
+ }
+
+ // Slow path: check if the package is listed among the system modules.
+ final PackageManager pm = mInjector.getPackageManager();
+ boolean isSystemModule = false;
+ try {
+ isSystemModule = pm.getModuleInfo(packageName, 0 /* flags */) != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+
+ if (!isSystemModule) {
+ try {
+ final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ // Check if the package is contained in an APEX. There is no public API to properly
+ // check whether a given APK package comes from an APEX registered as module.
+ // Therefore we conservatively assume that any package scanned from an /apex path is
+ // a system package.
+ isSystemModule = pkg != null && pkg.applicationInfo.sourceDir.startsWith(
+ Environment.getApexDirectory().getAbsolutePath());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ // Update the cache.
+ synchronized (mLock) {
+ mSystemModulesCache.put(packageName, isSystemModule);
+ }
+ return isSystemModule;
+ }
+
+ private void registerForUidObservers() {
+ try {
+ mInjector.getIActivityManager().registerUidObserver(mUidObserver,
+ UID_OBSERVER_ACTIVE | UID_OBSERVER_GONE | UID_OBSERVER_IDLE
+ | UID_OBSERVER_PROCSTATE, PROCESS_STATE_FOREGROUND_SERVICE, "android");
+ } catch (RemoteException e) {
+ // Intra-process call, it won't happen.
+ }
+ }
+
+ /**
+ * Called when initializing a user.
+ */
+ private void refreshAppRestrictionLevelForUser(@UserIdInt int userId, int reason,
+ int subReason) {
+ final List<AppStandbyInfo> appStandbyInfos = mInjector.getAppStandbyInternal()
+ .getAppStandbyBuckets(userId);
+ if (ArrayUtils.isEmpty(appStandbyInfos)) {
+ return;
+ }
+
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Refreshing restriction levels of user " + userId);
+ }
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+ for (AppStandbyInfo info: appStandbyInfos) {
+ final int uid = pm.getPackageUid(info.mPackageName, STOCK_PM_FLAGS, userId);
+ if (uid < 0) {
+ // Shouldn't happen.
+ Slog.e(TAG, "Unable to find " + info.mPackageName + "/u" + userId);
+ continue;
+ }
+ final @RestrictionLevel int level = calcAppRestrictionLevel(
+ userId, uid, info.mPackageName, info.mStandbyBucket, false, false);
+ applyRestrictionLevel(info.mPackageName, uid, level,
+ info.mStandbyBucket, true, reason, subReason);
+ }
+ }
+
+ void refreshAppRestrictionLevelForUid(int uid, int reason, int subReason,
+ boolean allowRequestBgRestricted) {
+ final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packages)) {
+ return;
+ }
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final int userId = UserHandle.getUserId(uid);
+ final long now = SystemClock.elapsedRealtime();
+ for (String pkg: packages) {
+ final int curBucket = appStandbyInternal.getAppStandbyBucket(pkg, userId, now, false);
+ final @RestrictionLevel int level = calcAppRestrictionLevel(userId, uid, pkg,
+ curBucket, allowRequestBgRestricted, true);
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Proposed restriction level of " + pkg + "/"
+ + UserHandle.formatUid(uid) + ": "
+ + ActivityManager.restrictionLevelToName(level));
+ }
+ applyRestrictionLevel(pkg, uid, level, curBucket, true, reason, subReason);
+ }
+ }
+
+ private @RestrictionLevel int calcAppRestrictionLevel(@UserIdInt int userId, int uid,
+ String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
+ boolean allowRequestBgRestricted, boolean calcTrackers) {
+ if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
+ return RESTRICTION_LEVEL_HIBERNATION;
+ }
+ @RestrictionLevel int level;
+ switch (standbyBucket) {
+ case STANDBY_BUCKET_EXEMPTED:
+ level = RESTRICTION_LEVEL_EXEMPTED;
+ break;
+ case STANDBY_BUCKET_NEVER:
+ level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ break;
+ case STANDBY_BUCKET_ACTIVE:
+ case STANDBY_BUCKET_WORKING_SET:
+ case STANDBY_BUCKET_FREQUENT:
+ case STANDBY_BUCKET_RARE:
+ case STANDBY_BUCKET_RESTRICTED:
+ default:
+ if (mInjector.getAppStateTracker()
+ .isAppBackgroundRestricted(uid, packageName)) {
+ return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ }
+ level = mConstantsObserver.mRestrictedBucketEnabled
+ && standbyBucket == STANDBY_BUCKET_RESTRICTED
+ ? RESTRICTION_LEVEL_RESTRICTED_BUCKET
+ : RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+ if (calcTrackers) {
+ @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName);
+ if (l == RESTRICTION_LEVEL_EXEMPTED) {
+ return RESTRICTION_LEVEL_EXEMPTED;
+ }
+ level = Math.max(l, level);
+ if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ // This level can't be entered without user consent
+ if (allowRequestBgRestricted) {
+ mBgHandler.obtainMessage(BgHandler.MSG_REQUEST_BG_RESTRICTED,
+ uid, 0, packageName).sendToTarget();
+ }
+ // Lower the level.
+ level = RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+ }
+ }
+ break;
+ }
+ return level;
+ }
+
+ /**
+ * Ask each of the trackers for their proposed restriction levels for the given uid/package,
+ * and return the most restrictive level.
+ *
+ * <p>Note, it's different from the {@link #getRestrictionLevel} where it returns the least
+ * restrictive level. We're returning the most restrictive level here because each tracker
+ * monitors certain dimensions of the app, the abusive behaviors could be detected in one or
+ * more of these dimensions, but not necessarily all of them. </p>
+ */
+ private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName) {
+ @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
+ final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled;
+ for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) {
+ @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy()
+ .getProposedRestrictionLevel(packageName, uid);
+ if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+ }
+ level = Math.max(level, l);
+ }
+ return level;
+ }
+
+ /**
+ * Get the restriction level of the given UID, if it hosts multiple packages,
+ * return least restricted one (or if any of them is exempted).
+ */
+ @RestrictionLevel int getRestrictionLevel(int uid) {
+ return mRestrictionSettings.getRestrictionLevel(uid);
+ }
+
+ /**
+ * Get the restriction level of the given UID and package.
+ */
+ @RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
+ return mRestrictionSettings.getRestrictionLevel(uid, packageName);
+ }
+
+ /**
+ * Get the restriction level of the given package in given user id.
+ */
+ @RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
+ return mRestrictionSettings.getRestrictionLevel(packageName, userId);
+ }
+
+ /**
+ * @return The total foreground service durations for the given package/uid with given
+ * foreground service type, or the total durations regardless the type if the given type is 0.
+ */
+ long getForegroundServiceTotalDurations(String packageName, int uid, long now,
+ @ForegroundServiceType int serviceType) {
+ return mInjector.getAppFGSTracker().getTotalDurations(packageName, uid, now,
+ foregroundServiceTypeToIndex(serviceType));
+ }
+
+ /**
+ * @return The total foreground service durations for the given uid with given
+ * foreground service type, or the total durations regardless the type if the given type is 0.
+ */
+ long getForegroundServiceTotalDurations(int uid, long now,
+ @ForegroundServiceType int serviceType) {
+ return mInjector.getAppFGSTracker().getTotalDurations(uid, now,
+ foregroundServiceTypeToIndex(serviceType));
+ }
+
+ /**
+ * @return The foreground service durations since given timestamp for the given package/uid
+ * with given foreground service type, or the total durations regardless the type if the given
+ * type is 0.
+ */
+ long getForegroundServiceTotalDurationsSince(String packageName, int uid, long since, long now,
+ @ForegroundServiceType int serviceType) {
+ return mInjector.getAppFGSTracker().getTotalDurationsSince(packageName, uid, since, now,
+ foregroundServiceTypeToIndex(serviceType));
+ }
+
+ /**
+ * @return The foreground service durations since given timestamp for the given uid with given
+ * foreground service type, or the total durations regardless the type if the given type is 0.
+ */
+ long getForegroundServiceTotalDurationsSince(int uid, long since, long now,
+ @ForegroundServiceType int serviceType) {
+ return mInjector.getAppFGSTracker().getTotalDurationsSince(uid, since, now,
+ foregroundServiceTypeToIndex(serviceType));
+ }
+
+ /**
+ * @return The total durations for the given package/uid with active media session.
+ */
+ long getMediaSessionTotalDurations(String packageName, int uid, long now) {
+ return mInjector.getAppMediaSessionTracker().getTotalDurations(packageName, uid, now);
+ }
+
+ /**
+ * @return The total durations for the given uid with active media session.
+ */
+ long getMediaSessionTotalDurations(int uid, long now) {
+ return mInjector.getAppMediaSessionTracker().getTotalDurations(uid, now);
+ }
+
+ /**
+ * @return The durations since given timestamp for the given package/uid with
+ * active media session.
+ */
+ long getMediaSessionTotalDurationsSince(String packageName, int uid, long since, long now) {
+ return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(packageName, uid, since,
+ now);
+ }
+
+ /**
+ * @return The durations since given timestamp for the given uid with active media session.
+ */
+ long getMediaSessionTotalDurationsSince(int uid, long since, long now) {
+ return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(uid, since, now);
+ }
+
+ /**
+ * @return The durations over the given window, where the given package/uid has either
+ * foreground services with type "mediaPlayback" running, or active media session running.
+ */
+ long getCompositeMediaPlaybackDurations(String packageName, int uid, long now, long window) {
+ final long since = Math.max(0, now - window);
+ final long mediaPlaybackDuration = Math.max(
+ getMediaSessionTotalDurationsSince(packageName, uid, since, now),
+ getForegroundServiceTotalDurationsSince(packageName, uid, since, now,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
+ return mediaPlaybackDuration;
+ }
+
+ /**
+ * @return The durations over the given window, where the given uid has either foreground
+ * services with type "mediaPlayback" running, or active media session running.
+ */
+ long getCompositeMediaPlaybackDurations(int uid, long now, long window) {
+ final long since = Math.max(0, now - window);
+ final long mediaPlaybackDuration = Math.max(
+ getMediaSessionTotalDurationsSince(uid, since, now),
+ getForegroundServiceTotalDurationsSince(uid, since, now,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
+ return mediaPlaybackDuration;
+ }
+
+ /**
+ * @return If the given package/uid has an active foreground service running.
+ */
+ boolean hasForegroundServices(String packageName, int uid) {
+ return mInjector.getAppFGSTracker().hasForegroundServices(packageName, uid);
+ }
+
+ /**
+ * @return If the given uid has an active foreground service running.
+ */
+ boolean hasForegroundServices(int uid) {
+ return mInjector.getAppFGSTracker().hasForegroundServices(uid);
+ }
+
+ /**
+ * @return The to-be-exempted battery usage of the given UID in the given duration; it could
+ * be considered as "exempted" due to various use cases, i.e. media playback.
+ */
+ double getUidBatteryExemptedUsageSince(int uid, long since, long now) {
+ return mInjector.getAppBatteryExemptionTracker()
+ .getUidBatteryExemptedUsageSince(uid, since, now);
+ }
+
+ /**
+ * @return The total battery usage of the given UID since the system boots.
+ */
+ double getUidBatteryUsage(int uid) {
+ return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid);
+ }
+
+ interface UidBatteryUsageProvider {
+ /**
+ * @return The total battery usage of the given UID since the system boots.
+ */
+ double getUidBatteryUsage(int uid);
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
+ prefix = " " + prefix;
+ synchronized (mLock) {
+ mRestrictionSettings.dumpLocked(pw, prefix);
+ }
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ pw.println();
+ mAppStateTrackers.get(i).dump(pw, prefix);
+ }
+ }
+
+ private void applyRestrictionLevel(String pkgName, int uid, @RestrictionLevel int level,
+ int curBucket, boolean allowUpdateBucket, int reason, int subReason) {
+ int curLevel;
+ int prevReason;
+ synchronized (mLock) {
+ curLevel = getRestrictionLevel(uid, pkgName);
+ if (curLevel == level) {
+ // Nothing to do.
+ return;
+ }
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Updating the restriction level of " + pkgName + "/"
+ + UserHandle.formatUid(uid) + " from "
+ + ActivityManager.restrictionLevelToName(curLevel) + " to "
+ + ActivityManager.restrictionLevelToName(level)
+ + " reason=" + reason + ", subReason=" + subReason);
+ }
+
+ prevReason = mRestrictionSettings.getReason(pkgName, uid);
+ mRestrictionSettings.update(pkgName, uid, level, reason, subReason);
+ }
+
+ if (!allowUpdateBucket || curBucket == STANDBY_BUCKET_EXEMPTED) {
+ return;
+ }
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
+ && curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ if (!mConstantsObserver.mRestrictedBucketEnabled
+ || !mConstantsObserver.mBgAutoRestrictedBucket) {
+ return;
+ }
+ // Moving the app standby bucket to restricted in the meanwhile.
+ if (DEBUG_BG_RESTRICTION_CONTROLLER
+ && level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ Slog.i(TAG, pkgName + "/" + UserHandle.formatUid(uid)
+ + " is bg-restricted, moving to restricted standby bucket");
+ }
+ if (curBucket != STANDBY_BUCKET_RESTRICTED) {
+ // restrict the app if it hasn't done so.
+ boolean doIt = true;
+ synchronized (mLock) {
+ final int index = mActiveUids.indexOfKey(uid, pkgName);
+ if (index >= 0) {
+ // It's currently active, enqueue it.
+ mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp(
+ pkgName, UserHandle.getUserId(uid), reason, subReason));
+ doIt = false;
+ }
+ }
+ if (doIt) {
+ appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid),
+ reason, subReason);
+ }
+ }
+ } else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
+ && level < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ // Moved out of the background-restricted state.
+ if (curBucket != STANDBY_BUCKET_RARE) {
+ synchronized (mLock) {
+ final int index = mActiveUids.indexOfKey(uid, pkgName);
+ if (index >= 0) {
+ mActiveUids.add(uid, pkgName, null);
+ }
+ }
+ appStandbyInternal.maybeUnrestrictApp(pkgName, UserHandle.getUserId(uid),
+ prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK,
+ reason, subReason);
+ }
+ }
+ }
+
+ private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+ // Firstly, notify the trackers.
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i)
+ .onBackgroundRestrictionChanged(uid, pkgName, restricted);
+ }
+
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final int userId = UserHandle.getUserId(uid);
+ final long now = SystemClock.elapsedRealtime();
+ final int curBucket = appStandbyInternal.getAppStandbyBucket(pkgName, userId, now, false);
+ if (restricted) {
+ // The app could fall into the background restricted with user consent only,
+ // so set the reason to it.
+ applyRestrictionLevel(pkgName, uid, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+ curBucket, true, REASON_MAIN_FORCED_BY_USER,
+ REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+ mBgHandler.obtainMessage(BgHandler.MSG_CANCEL_REQUEST_BG_RESTRICTED, uid, 0, pkgName)
+ .sendToTarget();
+ } else {
+ // Moved out of the background-restricted state, we'd need to check if it should
+ // stay in the restricted standby bucket.
+ final @RestrictionLevel int lastLevel =
+ mRestrictionSettings.getLastRestrictionLevel(uid, pkgName);
+ final int tentativeBucket = curBucket == STANDBY_BUCKET_EXEMPTED
+ ? STANDBY_BUCKET_EXEMPTED
+ : (lastLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
+ ? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE);
+ final @RestrictionLevel int level = calcAppRestrictionLevel(
+ UserHandle.getUserId(uid), uid, pkgName, tentativeBucket, false, true);
+
+ applyRestrictionLevel(pkgName, uid, level, curBucket, true,
+ REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION);
+ }
+ }
+
+ private void dispatchAppRestrictionLevelChanges(int uid, String pkgName,
+ @RestrictionLevel int newLevel) {
+ mRestrictionListeners.forEach(
+ l -> l.onRestrictionLevelChanged(uid, pkgName, newLevel));
+ }
+
+ private void dispatchAutoRestrictedBucketFeatureFlagChanged(boolean newValue) {
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final ArrayList<Runnable> pendingTasks = new ArrayList<>();
+ synchronized (mLock) {
+ mRestrictionSettings.forEachUidLocked(uid -> {
+ mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
+ if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ pendingTasks.add(newValue
+ ? () -> appStandbyInternal.restrictApp(pkgName,
+ UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
+ reason & REASON_SUB_MASK)
+ : () -> appStandbyInternal.maybeUnrestrictApp(pkgName,
+ UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
+ reason & REASON_SUB_MASK, REASON_MAIN_USAGE,
+ REASON_SUB_USAGE_SYSTEM_UPDATE));
+ }
+ });
+ });
+ }
+ for (int i = 0; i < pendingTasks.size(); i++) {
+ pendingTasks.get(i).run();
+ }
+ mRestrictionListeners.forEach(
+ l -> l.onAutoRestrictedBucketFeatureFlagChanged(newValue));
+ }
+
+ private void handleAppStandbyBucketChanged(int bucket, String packageName,
+ @UserIdInt int userId) {
+ final int uid = mInjector.getPackageManagerInternal().getPackageUid(
+ packageName, STOCK_PM_FLAGS, userId);
+ final @RestrictionLevel int level = calcAppRestrictionLevel(
+ userId, uid, packageName, bucket, false, false);
+ applyRestrictionLevel(packageName, uid, level, bucket, false,
+ REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_UNDEFINED);
+ }
+
+ void handleRequestBgRestricted(String packageName, int uid) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Requesting background restricted " + packageName + " "
+ + UserHandle.formatUid(uid));
+ }
+ mNotificationHelper.postRequestBgRestrictedIfNecessary(packageName, uid);
+ }
+
+ void handleCancelRequestBgRestricted(String packageName, int uid) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Cancelling requesting background restricted " + packageName + " "
+ + UserHandle.formatUid(uid));
+ }
+ mNotificationHelper.cancelRequestBgRestrictedIfNecessary(packageName, uid);
+ }
+
+ void handleUidProcStateChanged(int uid, int procState) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUidProcStateChanged(uid, procState);
+ }
+ }
+
+ void handleUidGone(int uid) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUidGone(uid);
+ }
+ }
+
+ static class NotificationHelper {
+ static final String PACKAGE_SCHEME = "package";
+ static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
+
+ static final int SUMMARY_NOTIFICATION_ID = SystemMessage.NOTE_ABUSIVE_BG_APPS_BASE;
+
+ static final int NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN = 0;
+ static final int NOTIFICATION_TYPE_LONG_RUNNING_FGS = 1;
+ static final int NOTIFICATION_TYPE_LAST = 2;
+
+ @IntDef(prefix = { "NOTIFICATION_TYPE_"}, value = {
+ NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
+ NOTIFICATION_TYPE_LONG_RUNNING_FGS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ static @interface NotificationType{}
+
+ static final String[] NOTIFICATION_TYPE_STRINGS = {
+ "Abusive current drain",
+ "Long-running FGS",
+ };
+
+ static final String ACTION_FGS_MANAGER_TRAMPOLINE =
+ "com.android.server.am.ACTION_FGS_MANAGER_TRAMPOLINE";
+
+ static String notificationTypeToString(@NotificationType int notificationType) {
+ return NOTIFICATION_TYPE_STRINGS[notificationType];
+ }
+
+ private final AppRestrictionController mBgController;
+ private final NotificationManager mNotificationManager;
+ private final Injector mInjector;
+ private final Object mLock;
+ private final Context mContext;
+
+ private final BroadcastReceiver mActionButtonReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ switch (intent.getAction()) {
+ case ACTION_FGS_MANAGER_TRAMPOLINE:
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ cancelRequestBgRestrictedIfNecessary(packageName, uid);
+ final Intent newIntent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
+ newIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mContext.sendBroadcastAsUser(newIntent,
+ UserHandle.of(UserHandle.getUserId(uid)));
+ break;
+ }
+ }
+ };
+
+ @GuardedBy("mLock")
+ private int mNotificationIDStepper = SUMMARY_NOTIFICATION_ID + 1;
+
+ NotificationHelper(AppRestrictionController controller) {
+ mBgController = controller;
+ mInjector = controller.mInjector;
+ mNotificationManager = mInjector.getNotificationManager();
+ mLock = controller.mLock;
+ mContext = mInjector.getContext();
+ }
+
+ void onSystemReady() {
+ mContext.registerReceiverForAllUsers(mActionButtonReceiver,
+ new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE),
+ MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler);
+ }
+
+ void postRequestBgRestrictedIfNecessary(String packageName, int uid) {
+ final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
+ intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
+
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null,
+ UserHandle.of(UserHandle.getUserId(uid)));
+ Notification.Action[] actions = null;
+ if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER
+ && mBgController.hasForegroundServices(packageName, uid)) {
+ final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE);
+ trampoline.setPackage("android");
+ trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ trampoline.putExtra(Intent.EXTRA_UID, uid);
+ final PendingIntent fgsMgrTrampoline = PendingIntent.getBroadcastAsUser(
+ mContext, 0, trampoline,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.CURRENT);
+ actions = new Notification.Action[] {
+ new Notification.Action.Builder(null,
+ mContext.getString(
+ com.android.internal.R.string.notification_action_check_bg_apps),
+ fgsMgrTrampoline)
+ .build()
+ };
+ }
+ postNotificationIfNecessary(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
+ com.android.internal.R.string.notification_title_abusive_bg_apps,
+ com.android.internal.R.string.notification_content_abusive_bg_apps,
+ pendingIntent, packageName, uid, actions);
+ }
+
+ void postLongRunningFgsIfNecessary(String packageName, int uid) {
+ PendingIntent pendingIntent;
+ if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER) {
+ final Intent intent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
+ intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.of(UserHandle.getUserId(uid)));
+ } else {
+ final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
+ intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
+ pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, UserHandle.of(UserHandle.getUserId(uid)));
+ }
+
+ postNotificationIfNecessary(NOTIFICATION_TYPE_LONG_RUNNING_FGS,
+ com.android.internal.R.string.notification_title_abusive_bg_apps,
+ com.android.internal.R.string.notification_content_long_running_fgs,
+ pendingIntent, packageName, uid, null);
+ }
+
+ int getNotificationIdIfNecessary(@NotificationType int notificationType,
+ String packageName, int uid) {
+ synchronized (mLock) {
+ final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+ .getRestrictionSettingsLocked(uid, packageName);
+ if (settings == null) {
+ return 0;
+ }
+
+ final long now = SystemClock.elapsedRealtime();
+ final long lastNotificationShownTimeElapsed =
+ settings.getLastNotificationTime(notificationType);
+ if (lastNotificationShownTimeElapsed != 0 && (lastNotificationShownTimeElapsed
+ + mBgController.mConstantsObserver.mBgNotificationMinIntervalMs > now)) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Not showing notification as last notification was shown "
+ + TimeUtils.formatDuration(now - lastNotificationShownTimeElapsed)
+ + " ago");
+ }
+ return 0;
+ }
+ settings.setLastNotificationTime(notificationType, now);
+ int notificationId = settings.getNotificationId(notificationType);
+ if (notificationId <= 0) {
+ notificationId = mNotificationIDStepper++;
+ settings.setNotificationId(notificationType, notificationId);
+ }
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Showing notification for " + packageName
+ + "/" + UserHandle.formatUid(uid)
+ + ", id=" + notificationId
+ + ", now=" + now
+ + ", lastShown=" + lastNotificationShownTimeElapsed);
+ }
+ return notificationId;
+ }
+ }
+
+ void postNotificationIfNecessary(@NotificationType int notificationType, int titleRes,
+ int messageRes, PendingIntent pendingIntent, String packageName, int uid,
+ @Nullable Notification.Action[] actions) {
+ int notificationId = getNotificationIdIfNecessary(notificationType, packageName, uid);
+ if (notificationId <= 0) {
+ return;
+ }
+
+ final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
+ final PackageManager pm = mInjector.getPackageManager();
+ final ApplicationInfo ai = pmi.getApplicationInfo(packageName, STOCK_PM_FLAGS,
+ SYSTEM_UID, UserHandle.getUserId(uid));
+ final String title = mContext.getString(titleRes);
+ final String message = mContext.getString(messageRes,
+ ai != null ? pm.getText(packageName, ai.labelRes, ai) : packageName);
+ final Icon icon = ai != null ? Icon.createWithResource(packageName, ai.icon) : null;
+
+ postNotification(notificationId, packageName, uid, title, message, icon, pendingIntent,
+ actions);
+ }
+
+ void postNotification(int notificationId, String packageName, int uid, String title,
+ String message, Icon icon, PendingIntent pendingIntent,
+ @Nullable Notification.Action[] actions) {
+ final UserHandle targetUser = UserHandle.of(UserHandle.getUserId(uid));
+ postSummaryNotification(targetUser);
+
+ final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+ ABUSIVE_BACKGROUND_APPS)
+ .setAutoCancel(true)
+ .setGroup(GROUP_KEY)
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setContentIntent(pendingIntent);
+ if (icon != null) {
+ notificationBuilder.setLargeIcon(icon);
+ }
+ if (actions != null) {
+ for (Notification.Action action : actions) {
+ notificationBuilder.addAction(action);
+ }
+ }
+
+ final Notification notification = notificationBuilder.build();
+ // Remember the package name for testing.
+ notification.extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+
+ mNotificationManager.notifyAsUser(null, notificationId, notification, targetUser);
+ }
+
+ private void postSummaryNotification(@NonNull UserHandle targetUser) {
+ final Notification summary = new Notification.Builder(mContext,
+ ABUSIVE_BACKGROUND_APPS)
+ .setGroup(GROUP_KEY)
+ .setGroupSummary(true)
+ .setStyle(new Notification.BigTextStyle())
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build();
+ mNotificationManager.notifyAsUser(null, SUMMARY_NOTIFICATION_ID, summary, targetUser);
+ }
+
+ void cancelRequestBgRestrictedIfNecessary(String packageName, int uid) {
+ synchronized (mLock) {
+ final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+ .getRestrictionSettingsLocked(uid, packageName);
+ if (settings != null) {
+ final int notificationId =
+ settings.getNotificationId(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN);
+ if (notificationId > 0) {
+ mNotificationManager.cancel(notificationId);
+ }
+ }
+ }
+ }
+
+ void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
+ synchronized (mLock) {
+ final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
+ .getRestrictionSettingsLocked(uid, packageName);
+ if (settings != null) {
+ final int notificationId =
+ settings.getNotificationId(NOTIFICATION_TYPE_LONG_RUNNING_FGS);
+ if (notificationId > 0) {
+ mNotificationManager.cancel(notificationId);
+ }
+ }
+ }
+ }
+ }
+
+ void handleUidInactive(int uid, boolean disabled) {
+ final ArrayList<Runnable> pendingTasks = mTmpRunnables;
+ synchronized (mLock) {
+ final int index = mActiveUids.indexOfKey(uid);
+ if (index < 0) {
+ return;
+ }
+ final int numPackages = mActiveUids.numElementsForKeyAt(index);
+ for (int i = 0; i < numPackages; i++) {
+ final Runnable pendingTask = mActiveUids.valueAt(index, i);
+ if (pendingTask != null) {
+ pendingTasks.add(pendingTask);
+ }
+ }
+ mActiveUids.deleteAt(index);
+ }
+ for (int i = 0, size = pendingTasks.size(); i < size; i++) {
+ pendingTasks.get(i).run();
+ }
+ pendingTasks.clear();
+ }
+
+ void handleUidActive(int uid) {
+ synchronized (mLock) {
+ final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
+ final int userId = UserHandle.getUserId(uid);
+ mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
+ if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp(pkgName,
+ userId, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK));
+ } else {
+ mActiveUids.add(uid, pkgName, null);
+ }
+ });
+ }
+ }
+
+ boolean isOnDeviceIdleAllowlist(int uid, boolean allowExceptIdle) {
+ final int appId = UserHandle.getAppId(uid);
+
+ final int[] allowlist = allowExceptIdle
+ ? mDeviceIdleExceptIdleAllowlist
+ : mDeviceIdleAllowlist;
+
+ return Arrays.binarySearch(allowlist, appId) >= 0;
+ }
+
+ void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
+ mDeviceIdleAllowlist = allAppids;
+ mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+ }
+
+ /**
+ * @return The reason code of whether or not the given UID should be exempted from background
+ * restrictions here.
+ *
+ * <p>
+ * Note: Call it with caution as it'll try to acquire locks in other services.
+ * </p>
+ */
+ @ReasonCode
+ int getBackgroundRestrictionExemptionReason(int uid) {
+ if (UserHandle.isCore(uid)) {
+ return REASON_SYSTEM_UID;
+ }
+ if (isOnDeviceIdleAllowlist(uid, false)) {
+ return REASON_ALLOWLISTED_PACKAGE;
+ }
+ final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
+ if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
+ return REASON_COMPANION_DEVICE_MANAGER;
+ }
+ if (UserManager.isDeviceInDemoMode(mContext)) {
+ return REASON_DEVICE_DEMO_MODE;
+ }
+ if (am.isDeviceOwner(uid)) {
+ return REASON_DEVICE_OWNER;
+ }
+ if (am.isProfileOwner(uid)) {
+ return REASON_PROFILE_OWNER;
+ }
+ final int uidProcState = am.getUidProcessState(uid);
+ if (uidProcState <= PROCESS_STATE_PERSISTENT) {
+ return REASON_PROC_STATE_PERSISTENT;
+ } else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
+ return REASON_PROC_STATE_PERSISTENT_UI;
+ }
+ final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
+ for (String pkg : packages) {
+ if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_VPN;
+ } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
+ uid, pkg) == AppOpsManager.MODE_ALLOWED) {
+ return REASON_OP_ACTIVATE_PLATFORM_VPN;
+ } else if (isSystemModule(pkg)) {
+ return REASON_SYSTEM_MODULE;
+ }
+ }
+ }
+ if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
+ return REASON_ROLE_DIALER;
+ }
+ if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
+ return REASON_ROLE_EMERGENCY;
+ }
+ return REASON_DENIED;
+ }
+
+ private boolean isRoleHeldByUid(@NonNull String roleName, int uid) {
+ synchronized (mLock) {
+ final ArrayList<String> roles = mUidRolesMapping.get(uid);
+ return roles != null && roles.indexOf(roleName) >= 0;
+ }
+ }
+
+ private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ final List<String> rolePkgs = mInjector.getRoleManager().getRoleHoldersAsUser(
+ roleName, user);
+ final ArraySet<Integer> roleUids = new ArraySet<>();
+ final int userId = user.getIdentifier();
+ if (rolePkgs != null) {
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
+ for (String pkg: rolePkgs) {
+ roleUids.add(pm.getPackageUid(pkg, STOCK_PM_FLAGS, userId));
+ }
+ }
+ synchronized (mLock) {
+ for (int i = mUidRolesMapping.size() - 1; i >= 0; i--) {
+ final int uid = mUidRolesMapping.keyAt(i);
+ if (UserHandle.getUserId(uid) != userId) {
+ continue;
+ }
+ final ArrayList<String> roles = mUidRolesMapping.valueAt(i);
+ final int index = roles.indexOf(roleName);
+ final boolean isRole = roleUids.contains(uid);
+ if (index >= 0) {
+ if (!isRole) { // Not holding this role anymore, remove it.
+ roles.remove(index);
+ if (roles.isEmpty()) {
+ mUidRolesMapping.removeAt(i);
+ }
+ }
+ } else if (isRole) { // Got this new role, add it.
+ roles.add(roleName);
+ roleUids.remove(uid);
+ }
+ }
+ for (int i = roleUids.size() - 1; i >= 0; i--) { // Take care of the leftovers.
+ final ArrayList<String> roles = new ArrayList<>();
+ roles.add(roleName);
+ mUidRolesMapping.put(roleUids.valueAt(i), roles);
+ }
+ }
+ }
+
+ /**
+ * @return The background handler of this controller.
+ */
+ Handler getBackgroundHandler() {
+ return mBgHandler;
+ }
+
+ /**
+ * @return The background handler thread of this controller.
+ */
+ @VisibleForTesting
+ HandlerThread getBackgroundHandlerThread() {
+ return mBgHandlerThread;
+ }
+
+ /**
+ * @return The global lock of this controller.
+ */
+ Object getLock() {
+ return mLock;
+ }
+
+ @VisibleForTesting
+ void addAppStateTracker(@NonNull BaseAppStateTracker tracker) {
+ mAppStateTrackers.add(tracker);
+ }
+
+ /**
+ * @return The tracker instance of the given class.
+ */
+ <T extends BaseAppStateTracker> T getAppStateTracker(Class<T> trackerClass) {
+ for (BaseAppStateTracker tracker : mAppStateTrackers) {
+ if (trackerClass.isAssignableFrom(tracker.getClass())) {
+ return (T) tracker;
+ }
+ }
+ return null;
+ }
+
+ void postLongRunningFgsIfNecessary(String packageName, int uid) {
+ mNotificationHelper.postLongRunningFgsIfNecessary(packageName, uid);
+ }
+
+ void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
+ mNotificationHelper.cancelLongRunningFGSNotificationIfNecessary(packageName, uid);
+ }
+
+ String getPackageName(int pid) {
+ return mInjector.getPackageName(pid);
+ }
+
+ static class BgHandler extends Handler {
+ static final int MSG_BACKGROUND_RESTRICTION_CHANGED = 0;
+ static final int MSG_APP_RESTRICTION_LEVEL_CHANGED = 1;
+ static final int MSG_APP_STANDBY_BUCKET_CHANGED = 2;
+ static final int MSG_USER_INTERACTION_STARTED = 3;
+ static final int MSG_REQUEST_BG_RESTRICTED = 4;
+ static final int MSG_UID_IDLE = 5;
+ static final int MSG_UID_ACTIVE = 6;
+ static final int MSG_UID_GONE = 7;
+ static final int MSG_UID_PROC_STATE_CHANGED = 8;
+ static final int MSG_CANCEL_REQUEST_BG_RESTRICTED = 9;
+
+ private final Injector mInjector;
+
+ BgHandler(Looper looper, Injector injector) {
+ super(looper);
+ mInjector = injector;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final AppRestrictionController c = mInjector
+ .getAppRestrictionController();
+ switch (msg.what) {
+ case MSG_BACKGROUND_RESTRICTION_CHANGED: {
+ c.handleBackgroundRestrictionChanged(msg.arg1, (String) msg.obj, msg.arg2 == 1);
+ } break;
+ case MSG_APP_RESTRICTION_LEVEL_CHANGED: {
+ c.dispatchAppRestrictionLevelChanges(msg.arg1, (String) msg.obj, msg.arg2);
+ } break;
+ case MSG_APP_STANDBY_BUCKET_CHANGED: {
+ c.handleAppStandbyBucketChanged(msg.arg2, (String) msg.obj, msg.arg1);
+ } break;
+ case MSG_USER_INTERACTION_STARTED: {
+ c.onUserInteractionStarted((String) msg.obj, msg.arg1);
+ } break;
+ case MSG_REQUEST_BG_RESTRICTED: {
+ c.handleRequestBgRestricted((String) msg.obj, msg.arg1);
+ } break;
+ case MSG_UID_IDLE: {
+ c.handleUidInactive(msg.arg1, msg.arg2 == 1);
+ } break;
+ case MSG_UID_ACTIVE: {
+ c.handleUidActive(msg.arg1);
+ } break;
+ case MSG_CANCEL_REQUEST_BG_RESTRICTED: {
+ c.handleCancelRequestBgRestricted((String) msg.obj, msg.arg1);
+ } break;
+ case MSG_UID_PROC_STATE_CHANGED: {
+ c.handleUidProcStateChanged(msg.arg1, msg.arg2);
+ } break;
+ case MSG_UID_GONE: {
+ // It also means this UID is inactive now.
+ c.handleUidInactive(msg.arg1, msg.arg2 == 1);
+ c.handleUidGone(msg.arg1);
+ } break;
+ }
+ }
+ }
+
+ static class Injector {
+ private final Context mContext;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private AppRestrictionController mAppRestrictionController;
+ private AppOpsManager mAppOpsManager;
+ private AppStandbyInternal mAppStandbyInternal;
+ private AppStateTracker mAppStateTracker;
+ private AppHibernationManagerInternal mAppHibernationInternal;
+ private IActivityManager mIActivityManager;
+ private UserManagerInternal mUserManagerInternal;
+ private PackageManagerInternal mPackageManagerInternal;
+ private NotificationManager mNotificationManager;
+ private RoleManager mRoleManager;
+ private AppBatteryTracker mAppBatteryTracker;
+ private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
+ private AppFGSTracker mAppFGSTracker;
+ private AppMediaSessionTracker mAppMediaSessionTracker;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ void initAppStateTrackers(AppRestrictionController controller) {
+ mAppRestrictionController = controller;
+ mAppBatteryTracker = new AppBatteryTracker(mContext, controller);
+ mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller);
+ mAppFGSTracker = new AppFGSTracker(mContext, controller);
+ mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller);
+ controller.mAppStateTrackers.add(mAppBatteryTracker);
+ controller.mAppStateTrackers.add(mAppBatteryExemptionTracker);
+ controller.mAppStateTrackers.add(mAppFGSTracker);
+ controller.mAppStateTrackers.add(mAppMediaSessionTracker);
+ controller.mAppStateTrackers.add(new AppBroadcastEventsTracker(mContext, controller));
+ controller.mAppStateTrackers.add(new AppBindServiceEventsTracker(mContext, controller));
+ }
+
+ ActivityManagerInternal getActivityManagerInternal() {
+ if (mActivityManagerInternal == null) {
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ }
+ return mActivityManagerInternal;
+ }
+
+ AppRestrictionController getAppRestrictionController() {
+ return mAppRestrictionController;
+ }
+
+ AppOpsManager getAppOpsManager() {
+ if (mAppOpsManager == null) {
+ mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
+ }
+ return mAppOpsManager;
+ }
+
+ AppStandbyInternal getAppStandbyInternal() {
+ if (mAppStandbyInternal == null) {
+ mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
+ }
+ return mAppStandbyInternal;
+ }
+
+ AppHibernationManagerInternal getAppHibernationInternal() {
+ if (mAppHibernationInternal == null) {
+ mAppHibernationInternal = LocalServices.getService(
+ AppHibernationManagerInternal.class);
+ }
+ return mAppHibernationInternal;
+ }
+
+ AppStateTracker getAppStateTracker() {
+ if (mAppStateTracker == null) {
+ mAppStateTracker = LocalServices.getService(AppStateTracker.class);
+ }
+ return mAppStateTracker;
+ }
+
+ IActivityManager getIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ UserManagerInternal getUserManagerInternal() {
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ return mUserManagerInternal;
+ }
+
+ PackageManagerInternal getPackageManagerInternal() {
+ if (mPackageManagerInternal == null) {
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+ return mPackageManagerInternal;
+ }
+
+ PackageManager getPackageManager() {
+ return getContext().getPackageManager();
+ }
+
+ NotificationManager getNotificationManager() {
+ if (mNotificationManager == null) {
+ mNotificationManager = getContext().getSystemService(NotificationManager.class);
+ }
+ return mNotificationManager;
+ }
+
+ RoleManager getRoleManager() {
+ if (mRoleManager == null) {
+ mRoleManager = getContext().getSystemService(RoleManager.class);
+ }
+ return mRoleManager;
+ }
+
+ AppFGSTracker getAppFGSTracker() {
+ return mAppFGSTracker;
+ }
+
+ AppMediaSessionTracker getAppMediaSessionTracker() {
+ return mAppMediaSessionTracker;
+ }
+
+ ActivityManagerService getActivityManagerService() {
+ return mAppRestrictionController.mActivityManagerService;
+ }
+
+ UidBatteryUsageProvider getUidBatteryUsageProvider() {
+ return mAppBatteryTracker;
+ }
+
+ AppBatteryExemptionTracker getAppBatteryExemptionTracker() {
+ return mAppBatteryExemptionTracker;
+ }
+
+ String getPackageName(int pid) {
+ final ActivityManagerService am = getActivityManagerService();
+ final ProcessRecord app;
+ synchronized (am.mPidsSelfLocked) {
+ app = am.mPidsSelfLocked.get(pid);
+ if (app != null) {
+ final ApplicationInfo ai = app.info;
+ if (ai != null) {
+ return ai.packageName;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ private void registerForSystemBroadcasts() {
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ switch (intent.getAction()) {
+ case Intent.ACTION_PACKAGE_ADDED: {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid >= 0) {
+ onUidAdded(uid);
+ }
+ }
+ } break;
+ case Intent.ACTION_PACKAGE_FULLY_REMOVED: {
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final Uri data = intent.getData();
+ String ssp;
+ if (uid >= 0 && data != null
+ && (ssp = data.getSchemeSpecificPart()) != null) {
+ onPackageRemoved(ssp, uid);
+ }
+ } break;
+ case Intent.ACTION_UID_REMOVED: {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid >= 0) {
+ onUidRemoved(uid);
+ }
+ }
+ } break;
+ case Intent.ACTION_USER_ADDED: {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId >= 0) {
+ onUserAdded(userId);
+ }
+ } break;
+ case Intent.ACTION_USER_STARTED: {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId >= 0) {
+ onUserStarted(userId);
+ }
+ } break;
+ case Intent.ACTION_USER_STOPPED: {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId >= 0) {
+ onUserStopped(userId);
+ }
+ } break;
+ case Intent.ACTION_USER_REMOVED: {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId >= 0) {
+ onUserRemoved(userId);
+ }
+ } break;
+ }
+ }
+ };
+ final IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ packageFilter.addDataScheme("package");
+ mContext.registerReceiverForAllUsers(broadcastReceiver, packageFilter, null, mBgHandler);
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_ADDED);
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(Intent.ACTION_UID_REMOVED);
+ mContext.registerReceiverForAllUsers(broadcastReceiver, userFilter, null, mBgHandler);
+ }
+
+ void forEachTracker(Consumer<BaseAppStateTracker> sink) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ sink.accept(mAppStateTrackers.get(i));
+ }
+ }
+
+ private void onUserAdded(@UserIdInt int userId) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUserAdded(userId);
+ }
+ }
+
+ private void onUserStarted(@UserIdInt int userId) {
+ refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
+ REASON_SUB_FORCED_USER_FLAG_INTERACTION);
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUserStarted(userId);
+ }
+ }
+
+ private void onUserStopped(@UserIdInt int userId) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUserStopped(userId);
+ }
+ }
+
+ private void onUserRemoved(@UserIdInt int userId) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUserRemoved(userId);
+ }
+ mRestrictionSettings.removeUser(userId);
+ }
+
+ private void onUidAdded(int uid) {
+ refreshAppRestrictionLevelForUid(uid, REASON_MAIN_FORCED_BY_SYSTEM,
+ REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED, false);
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUidAdded(uid);
+ }
+ }
+
+ private void onPackageRemoved(String pkgName, int uid) {
+ mRestrictionSettings.removePackage(pkgName, uid);
+ }
+
+ private void onUidRemoved(int uid) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUidRemoved(uid);
+ }
+ mRestrictionSettings.removeUid(uid);
+ }
+
+ boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
+ return mConstantsObserver.mBgAutoRestrictedBucket;
+ }
+
+ private void onPropertiesChanged(String name) {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onPropertiesChanged(name);
+ }
+ }
+
+ private void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
+ final int uid = mInjector.getPackageManagerInternal()
+ .getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onUserInteractionStarted(packageName, uid);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateDurations.java b/services/core/java/com/android/server/am/BaseAppStateDurations.java
new file mode 100644
index 0000000..4d3b4dd
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateDurations.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the accumulated durations of certain events; supports tracking event
+ * start/stop, trim.
+ */
+abstract class BaseAppStateDurations<T extends BaseTimeEvent> extends BaseAppStateTimeEvents<T> {
+ static final boolean DEBUG_BASE_APP_STATE_DURATIONS = false;
+
+ BaseAppStateDurations(int uid, @NonNull String packageName, int numOfEventTypes,
+ @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+ }
+
+ BaseAppStateDurations(@NonNull BaseAppStateDurations other) {
+ super(other);
+ }
+
+ /**
+ * Add a start/stop event.
+ */
+ void addEvent(boolean start, @NonNull T event, int index) {
+ if (mEvents[index] == null) {
+ mEvents[index] = new LinkedList<>();
+ }
+ final LinkedList<T> events = mEvents[index];
+ final int size = events.size();
+ final boolean active = isActive(index);
+
+ if (DEBUG_BASE_APP_STATE_DURATIONS && !start && !active) {
+ Slog.wtf(mTag, "Under-counted start event");
+ return;
+ }
+ if (start != active) {
+ // Only record the event time if it's not the same state as now
+ events.add(event);
+ }
+ trimEvents(getEarliest(event.getTimestamp()), index);
+ }
+
+ @Override
+ void trimEvents(long earliest, int index) {
+ trimEvents(earliest, mEvents[index]);
+ }
+
+ void trimEvents(long earliest, LinkedList<T> events) {
+ if (events == null) {
+ return;
+ }
+ while (events.size() > 1) {
+ final T current = events.peek();
+ if (current.getTimestamp() >= earliest) {
+ return; // All we have are newer than the given timestamp.
+ }
+ // Check the timestamp of stop event.
+ if (events.get(1).getTimestamp() > earliest) {
+ // Trim the duration by moving the start time.
+ events.get(0).trimTo(earliest);
+ return;
+ }
+ // Discard the 1st duration as it's older than the given timestamp.
+ events.pop();
+ events.pop();
+ }
+ if (events.size() == 1) {
+ // Trim the duration by moving the start time.
+ events.get(0).trimTo(Math.max(earliest, events.peek().getTimestamp()));
+ }
+ }
+
+ /**
+ * Merge the two given duration table and return the result.
+ */
+ @Override
+ LinkedList<T> add(LinkedList<T> durations, LinkedList<T> otherDurations) {
+ if (otherDurations == null || otherDurations.size() == 0) {
+ return durations;
+ }
+ if (durations == null || durations.size() == 0) {
+ return (LinkedList<T>) otherDurations.clone();
+ }
+ final Iterator<T> itl = durations.iterator();
+ final Iterator<T> itr = otherDurations.iterator();
+ T l = itl.next(), r = itr.next();
+ LinkedList<T> dest = new LinkedList<>();
+ boolean actl = false, actr = false;
+ for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+ lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+ final boolean actCur = actl || actr;
+ final T earliest;
+ if (lts == rts) {
+ earliest = l;
+ actl = !actl;
+ actr = !actr;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ } else if (lts < rts) {
+ earliest = l;
+ actl = !actl;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ } else {
+ earliest = r;
+ actr = !actr;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ }
+ if (actCur != (actl || actr)) {
+ dest.add((T) earliest.clone());
+ }
+ }
+ return dest;
+ }
+
+ /**
+ * Subtract the other durations from the this duration table at given index
+ */
+ void subtract(BaseAppStateDurations otherDurations, int thisIndex, int otherIndex) {
+ if (mEvents.length <= thisIndex || mEvents[thisIndex] == null
+ || otherDurations.mEvents.length <= otherIndex
+ || otherDurations.mEvents[otherIndex] == null) {
+ if (DEBUG_BASE_APP_STATE_DURATIONS) {
+ Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + otherDurations
+ + ", thisIndex=" + thisIndex + ", otherIndex=" + otherIndex);
+ }
+ return;
+ }
+ mEvents[thisIndex] = subtract(mEvents[thisIndex], otherDurations.mEvents[otherIndex]);
+ }
+
+ /**
+ * Subtract the other durations at given index from the this duration table at all indexes.
+ */
+ void subtract(BaseAppStateDurations otherDurations, int otherIndex) {
+ if (otherDurations.mEvents.length <= otherIndex
+ || otherDurations.mEvents[otherIndex] == null) {
+ if (DEBUG_BASE_APP_STATE_DURATIONS) {
+ Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + otherDurations
+ + ", otherIndex=" + otherIndex);
+ }
+ return;
+ }
+ for (int i = 0; i < mEvents.length; i++) {
+ if (mEvents[i] != null) {
+ mEvents[i] = subtract(mEvents[i], otherDurations.mEvents[otherIndex]);
+ }
+ }
+ }
+
+ /**
+ * Subtract the other durations from the given duration table and return the new one.
+ */
+ LinkedList<T> subtract(LinkedList<T> durations, LinkedList<T> otherDurations) {
+ if (otherDurations == null || otherDurations.size() == 0
+ || durations == null || durations.size() == 0) {
+ return durations;
+ }
+ final Iterator<T> itl = durations.iterator();
+ final Iterator<T> itr = otherDurations.iterator();
+ T l = itl.next(), r = itr.next();
+ LinkedList<T> dest = new LinkedList<>();
+ boolean actl = false, actr = false;
+ for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+ lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+ final boolean actCur = actl && !actr;
+ final T earliest;
+ if (lts == rts) {
+ earliest = l;
+ actl = !actl;
+ actr = !actr;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ } else if (lts < rts) {
+ earliest = l;
+ actl = !actl;
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ } else {
+ earliest = r;
+ actr = !actr;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ }
+ if (actCur != (actl && !actr)) {
+ dest.add((T) earliest.clone());
+ }
+ }
+ return dest;
+ }
+
+ long getTotalDurations(long now, int index) {
+ return getTotalDurationsSince(getEarliest(0), now, index);
+ }
+
+ long getTotalDurationsSince(long since, long now, int index) {
+ final LinkedList<T> events = mEvents[index];
+ if (events == null || events.size() == 0) {
+ return 0L;
+ }
+ boolean active = true;
+ long last = 0;
+ long duration = 0;
+ for (T event : events) {
+ if (event.getTimestamp() < since || active) {
+ last = event.getTimestamp();
+ } else {
+ duration += Math.max(0, event.getTimestamp() - Math.max(last, since));
+ }
+ active = !active;
+ }
+ if ((events.size() & 1) == 1) {
+ duration += Math.max(0, now - Math.max(last, since));
+ }
+ return duration;
+ }
+
+ boolean isActive(int index) {
+ return mEvents[index] != null && (mEvents[index].size() & 1) == 1;
+ }
+
+ @Override
+ String formatEventSummary(long now, int index) {
+ return TimeUtils.formatDuration(getTotalDurations(now, index));
+ }
+
+ @Override
+ public String toString() {
+ return mPackageName + "/" + UserHandle.formatUid(mUid)
+ + " isActive[0]=" + isActive(0)
+ + " totalDurations[0]=" + getTotalDurations(SystemClock.elapsedRealtime(), 0);
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java
new file mode 100644
index 0000000..cc89e84
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.BaseAppStateEvents.MaxTrackingDurationConfig;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain binary state event of apps.
+ */
+abstract class BaseAppStateDurationsTracker
+ <T extends BaseAppStateEventsPolicy, U extends BaseAppStateDurations>
+ extends BaseAppStateEventsTracker<T, U> {
+ static final boolean DEBUG_BASE_APP_STATE_DURATION_TRACKER = false;
+
+ static final int EVENT_TYPE_MEDIA_SESSION = 0;
+ static final int EVENT_TYPE_FGS_MEDIA_PLAYBACK = 1;
+ static final int EVENT_TYPE_FGS_LOCATION = 2;
+ static final int EVENT_NUM = 3;
+
+ final ArrayList<EventListener> mEventListeners = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ final SparseArray<UidStateDurations> mUidStateDurations = new SparseArray<>();
+
+ interface EventListener {
+ void onNewEvent(int uid, String packageName, boolean start, long now, int eventType);
+ }
+
+ BaseAppStateDurationsTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<T>> injector, Object outerContext) {
+ super(context, controller, injector, outerContext);
+ }
+
+ @Override
+ void onUidProcStateChanged(final int uid, final int procState) {
+ synchronized (mLock) {
+ if (mPkgEvents.getMap().indexOfKey(uid) < 0) {
+ // If we're not tracking its events, ignore its UID state changes.
+ return;
+ }
+ onUidProcStateChangedUncheckedLocked(uid, procState);
+ UidStateDurations uidStateDurations = mUidStateDurations.get(uid);
+ if (uidStateDurations == null) {
+ uidStateDurations = new UidStateDurations(uid, mInjector.getPolicy());
+ mUidStateDurations.put(uid, uidStateDurations);
+ }
+ uidStateDurations.addEvent(procState < PROCESS_STATE_FOREGROUND_SERVICE,
+ SystemClock.elapsedRealtime());
+ }
+ }
+
+ @Override
+ void onUidGone(final int uid) {
+ onUidProcStateChanged(uid, PROCESS_STATE_NONEXISTENT);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void trimLocked(long earliest) {
+ super.trimLocked(earliest);
+ for (int i = mUidStateDurations.size() - 1; i >= 0; i--) {
+ final UidStateDurations u = mUidStateDurations.valueAt(i);
+ u.trim(earliest);
+ if (u.isEmpty()) {
+ mUidStateDurations.removeAt(i);
+ }
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void onUntrackingUidLocked(int uid) {
+ mUidStateDurations.remove(uid);
+ }
+
+ void registerEventListener(@NonNull EventListener listener) {
+ synchronized (mLock) {
+ mEventListeners.add(listener);
+ }
+ }
+
+ void notifyListenersOnEvent(int uid, String packageName,
+ boolean start, long now, int eventType) {
+ synchronized (mLock) {
+ for (int i = 0, size = mEventListeners.size(); i < size; i++) {
+ mEventListeners.get(i).onNewEvent(uid, packageName, start, now, eventType);
+ }
+ }
+ }
+
+ long getTotalDurations(String packageName, int uid, long now, int index, boolean bgOnly) {
+ synchronized (mLock) {
+ final U durations = mPkgEvents.get(uid, packageName);
+ if (durations == null) {
+ return 0;
+ }
+ if (bgOnly) {
+ final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+ if (uidDurations != null && !uidDurations.isEmpty()) {
+ final U res = createAppStateEvents(durations);
+ res.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+ return res.getTotalDurations(now, index);
+ }
+ }
+ return durations.getTotalDurations(now, index);
+ }
+ }
+
+ long getTotalDurations(String packageName, int uid, long now, int index) {
+ return getTotalDurations(packageName, uid, now, index, true /* bgOnly */);
+ }
+
+ long getTotalDurations(String packageName, int uid, long now) {
+ return getTotalDurations(packageName, uid, now, SimplePackageDurations.DEFAULT_INDEX);
+ }
+
+ long getTotalDurations(int uid, long now, int index, boolean bgOnly) {
+ synchronized (mLock) {
+ final U durations = getUidEventsLocked(uid);
+ if (durations == null) {
+ return 0;
+ }
+ if (bgOnly) {
+ final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+ if (uidDurations != null && !uidDurations.isEmpty()) {
+ durations.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+ }
+ }
+ return durations.getTotalDurations(now, index);
+ }
+ }
+
+ long getTotalDurations(int uid, long now, int index) {
+ return getTotalDurations(uid, now, index, true /* bgOnly */);
+ }
+
+ long getTotalDurations(int uid, long now) {
+ return getTotalDurations(uid, now, SimplePackageDurations.DEFAULT_INDEX);
+ }
+
+ long getTotalDurationsSince(String packageName, int uid, long since, long now, int index,
+ boolean bgOnly) {
+ synchronized (mLock) {
+ final U durations = mPkgEvents.get(uid, packageName);
+ if (durations == null) {
+ return 0;
+ }
+ if (bgOnly) {
+ final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+ if (uidDurations != null && !uidDurations.isEmpty()) {
+ final U res = createAppStateEvents(durations);
+ res.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+ return res.getTotalDurationsSince(since, now, index);
+ }
+ }
+ return durations.getTotalDurationsSince(since, now, index);
+ }
+ }
+
+ long getTotalDurationsSince(String packageName, int uid, long since, long now, int index) {
+ return getTotalDurationsSince(packageName, uid, since, now, index, true /* bgOnly */);
+ }
+
+ long getTotalDurationsSince(String packageName, int uid, long since, long now) {
+ return getTotalDurationsSince(packageName, uid, since, now,
+ SimplePackageDurations.DEFAULT_INDEX);
+ }
+
+ long getTotalDurationsSince(int uid, long since, long now, int index, boolean bgOnly) {
+ synchronized (mLock) {
+ final U durations = getUidEventsLocked(uid);
+ if (durations == null) {
+ return 0;
+ }
+ if (bgOnly) {
+ final UidStateDurations uidDurations = mUidStateDurations.get(uid);
+ if (uidDurations != null && !uidDurations.isEmpty()) {
+ durations.subtract(uidDurations, index, UidStateDurations.DEFAULT_INDEX);
+ }
+ }
+ return durations.getTotalDurationsSince(since, now, index);
+ }
+ }
+
+ long getTotalDurationsSince(int uid, long since, long now, int index) {
+ return getTotalDurationsSince(uid, since, now, index, true /* bgOnly */);
+ }
+
+ long getTotalDurationsSince(int uid, long since, long now) {
+ return getTotalDurationsSince(uid, since, now, SimplePackageDurations.DEFAULT_INDEX);
+ }
+
+ @VisibleForTesting
+ @Override
+ void reset() {
+ super.reset();
+ synchronized (mLock) {
+ mUidStateDurations.clear();
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dumpEventLocked(PrintWriter pw, String prefix, U events, long now) {
+ final UidStateDurations uidDurations = mUidStateDurations.get(events.mUid);
+ pw.print(" " + prefix);
+ pw.println("(bg only)");
+ if (uidDurations == null || uidDurations.isEmpty()) {
+ events.dump(pw, " " + prefix, now);
+ return;
+ }
+ final U bgEvents = createAppStateEvents(events);
+ bgEvents.subtract(uidDurations, SimplePackageDurations.DEFAULT_INDEX);
+ bgEvents.dump(pw, " " + prefix, now);
+ pw.print(" " + prefix);
+ pw.println("(fg + bg)");
+ events.dump(pw, " " + prefix, now);
+ }
+
+ /**
+ * Simple duration table, with only one track of durations.
+ */
+ static class SimplePackageDurations extends BaseAppStateDurations<BaseTimeEvent> {
+ static final int DEFAULT_INDEX = 0;
+
+ SimplePackageDurations(int uid, String packageName,
+ MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, 1, TAG, maxTrackingDurationConfig);
+ mEvents[DEFAULT_INDEX] = new LinkedList<BaseTimeEvent>();
+ }
+
+ SimplePackageDurations(SimplePackageDurations other) {
+ super(other);
+ }
+
+ void addEvent(boolean active, long now) {
+ addEvent(active, new BaseTimeEvent(now), DEFAULT_INDEX);
+ }
+
+ long getTotalDurations(long now) {
+ return getTotalDurations(now, DEFAULT_INDEX);
+ }
+
+ long getTotalDurationsSince(long since, long now) {
+ return getTotalDurationsSince(since, now, DEFAULT_INDEX);
+ }
+
+ boolean isActive() {
+ return isActive(DEFAULT_INDEX);
+ }
+
+ @Override
+ String formatEventTypeLabel(int index) {
+ return "";
+ }
+ }
+
+ static class UidStateDurations extends SimplePackageDurations {
+ UidStateDurations(int uid, MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, "", maxTrackingDurationConfig);
+ }
+
+ UidStateDurations(UidStateDurations other) {
+ super(other);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateEvents.java b/services/core/java/com/android/server/am/BaseAppStateEvents.java
new file mode 100644
index 0000000..a754059
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateEvents.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.os.PowerExemptionManager.REASON_DENIED;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the occurrences of certain events.
+ */
+abstract class BaseAppStateEvents<E> {
+ static final boolean DEBUG_BASE_APP_STATE_EVENTS = false;
+ final int mUid;
+ final @NonNull String mPackageName;
+ final @NonNull String mTag;
+ final @NonNull MaxTrackingDurationConfig mMaxTrackingDurationConfig;
+
+ /**
+ * The events we're tracking.
+ *
+ * <p>
+ * The meaning of the events is up to the derived classes, i.e., it could be a series of
+ * individual events, or a series of event pairs (i.e., start/stop event). The implementations
+ * of {@link #add}, {@link #trim} etc. in this class are based on the individual events.
+ * </p>
+ */
+ final LinkedList<E>[] mEvents;
+
+ /**
+ * In case the data we're tracking here is ignored, here is why.
+ */
+ @ReasonCode int mExemptReason = REASON_DENIED;
+
+ BaseAppStateEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+ @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ mUid = uid;
+ mPackageName = packageName;
+ mTag = tag;
+ mMaxTrackingDurationConfig = maxTrackingDurationConfig;
+ mEvents = new LinkedList[numOfEventTypes];
+ }
+
+ BaseAppStateEvents(@NonNull BaseAppStateEvents other) {
+ mUid = other.mUid;
+ mPackageName = other.mPackageName;
+ mTag = other.mTag;
+ mMaxTrackingDurationConfig = other.mMaxTrackingDurationConfig;
+ mEvents = new LinkedList[other.mEvents.length];
+ for (int i = 0; i < mEvents.length; i++) {
+ if (other.mEvents[i] != null) {
+ mEvents[i] = new LinkedList<E>(other.mEvents[i]);
+ }
+ }
+ }
+
+ /**
+ * Add an individual event.
+ */
+ void addEvent(E event, long now, int index) {
+ if (mEvents[index] == null) {
+ mEvents[index] = new LinkedList<E>();
+ }
+ final LinkedList<E> events = mEvents[index];
+ events.add(event);
+ trimEvents(getEarliest(now), index);
+ }
+
+ /**
+ * Remove/trim earlier events with start time older than the given timestamp.
+ */
+ void trim(long earliest) {
+ for (int i = 0; i < mEvents.length; i++) {
+ trimEvents(earliest, i);
+ }
+ }
+
+ /**
+ * Remove/trim earlier events with start time older than the given timestamp.
+ */
+ abstract void trimEvents(long earliest, int index);
+
+ /**
+ * @return {@code true} if there is no events being tracked.
+ */
+ boolean isEmpty() {
+ for (int i = 0; i < mEvents.length; i++) {
+ if (mEvents[i] != null && !mEvents[i].isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return {@code true} if there is no events being tracked.
+ */
+ boolean isEmpty(int index) {
+ return mEvents[index] == null || mEvents[index].isEmpty();
+ }
+
+ /**
+ * Merge the events table from another instance.
+ */
+ void add(BaseAppStateEvents other) {
+ if (mEvents.length != other.mEvents.length) {
+ if (DEBUG_BASE_APP_STATE_EVENTS) {
+ Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + other);
+ }
+ return;
+ }
+ for (int i = 0; i < mEvents.length; i++) {
+ mEvents[i] = add(mEvents[i], other.mEvents[i]);
+ }
+ }
+
+ @VisibleForTesting
+ LinkedList<E> getRawEvents(int index) {
+ return mEvents[index];
+ }
+
+ /**
+ * Merge the two given events table and return the result.
+ */
+ abstract LinkedList<E> add(LinkedList<E> events, LinkedList<E> otherEvents);
+
+ /**
+ * The number of events since the given time.
+ */
+ abstract int getTotalEventsSince(long since, long now, int index);
+
+ /**
+ * The total number of events we are tracking.
+ */
+ int getTotalEvents(long now, int index) {
+ return getTotalEventsSince(getEarliest(0), now, index);
+ }
+
+ /**
+ * @return The earliest possible time we're tracking with given timestamp.
+ */
+ long getEarliest(long now) {
+ return Math.max(0, now - mMaxTrackingDurationConfig.getMaxTrackingDuration());
+ }
+
+ void dump(PrintWriter pw, String prefix, @ElapsedRealtimeLong long nowElapsed) {
+ for (int i = 0; i < mEvents.length; i++) {
+ if (mEvents[i] == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.print(formatEventTypeLabel(i));
+ pw.println(formatEventSummary(nowElapsed, i));
+ }
+ }
+
+ String formatEventSummary(long now, int index) {
+ return Integer.toString(getTotalEvents(now, index));
+ }
+
+ String formatEventTypeLabel(int index) {
+ return Integer.toString(index) + ":";
+ }
+
+ @Override
+ public String toString() {
+ return mPackageName + "/" + UserHandle.formatUid(mUid)
+ + " totalEvents[0]=" + formatEventSummary(SystemClock.elapsedRealtime(), 0);
+ }
+
+ interface Factory<T extends BaseAppStateEvents> {
+ T createAppStateEvents(int uid, String packageName);
+ T createAppStateEvents(T other);
+ }
+
+ interface MaxTrackingDurationConfig {
+ /**
+ * @return The mximum duration we'd keep tracking.
+ */
+ long getMaxTrackingDuration();
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
new file mode 100644
index 0000000..3e1bcae
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.PowerExemptionManager;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.BaseAppStateEvents.MaxTrackingDurationConfig;
+import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain state event of apps.
+ */
+abstract class BaseAppStateEventsTracker
+ <T extends BaseAppStateEventsPolicy, U extends BaseAppStateEvents>
+ extends BaseAppStateTracker<T> implements BaseAppStateEvents.Factory<U> {
+ static final boolean DEBUG_BASE_APP_STATE_EVENTS_TRACKER = false;
+
+ @GuardedBy("mLock")
+ final UidProcessMap<U> mPkgEvents = new UidProcessMap<>();
+
+ @GuardedBy("mLock")
+ final ArraySet<Integer> mTopUids = new ArraySet<>();
+
+ BaseAppStateEventsTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<T>> injector, Object outerContext) {
+ super(context, controller, injector, outerContext);
+ }
+
+ @VisibleForTesting
+ void reset() {
+ synchronized (mLock) {
+ mPkgEvents.clear();
+ mTopUids.clear();
+ }
+ }
+
+ @GuardedBy("mLock")
+ U getUidEventsLocked(int uid) {
+ U events = null;
+ final ArrayMap<String, U> map = mPkgEvents.getMap().get(uid);
+ if (map == null) {
+ return null;
+ }
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final U event = map.valueAt(i);
+ if (event != null) {
+ if (events == null) {
+ events = createAppStateEvents(uid, event.mPackageName);
+ }
+ events.add(event);
+ }
+ }
+ return events;
+ }
+
+ void trim(long earliest) {
+ synchronized (mLock) {
+ trimLocked(earliest);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void trimLocked(long earliest) {
+ final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, U> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final U v = val.valueAt(j);
+ v.trim(earliest);
+ if (v.isEmpty()) {
+ val.removeAt(j);
+ }
+ }
+ if (val.size() == 0) {
+ map.removeAt(i);
+ }
+ }
+ }
+
+ boolean isUidOnTop(int uid) {
+ synchronized (mLock) {
+ return mTopUids.contains(uid);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onUntrackingUidLocked(int uid) {
+ }
+
+ @Override
+ void onUidProcStateChanged(final int uid, final int procState) {
+ synchronized (mLock) {
+ if (mPkgEvents.getMap().indexOfKey(uid) < 0) {
+ // If we're not tracking its events, ignore its UID state changes.
+ return;
+ }
+ onUidProcStateChangedUncheckedLocked(uid, procState);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void onUidProcStateChangedUncheckedLocked(final int uid, final int procState) {
+ if (procState < ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ mTopUids.add(uid);
+ } else {
+ mTopUids.remove(uid);
+ }
+ }
+
+ @Override
+ void onUidGone(final int uid) {
+ synchronized (mLock) {
+ mTopUids.remove(uid);
+ }
+ }
+
+ @Override
+ void onUidRemoved(final int uid) {
+ synchronized (mLock) {
+ mPkgEvents.getMap().remove(uid);
+ onUntrackingUidLocked(uid);
+ }
+ }
+
+ @Override
+ void onUserRemoved(final @UserIdInt int userId) {
+ synchronized (mLock) {
+ final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final int uid = map.keyAt(i);
+ if (UserHandle.getUserId(uid) == userId) {
+ map.removeAt(i);
+ onUntrackingUidLocked(uid);
+ }
+ }
+ }
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ final T policy = mInjector.getPolicy();
+ synchronized (mLock) {
+ final long now = SystemClock.elapsedRealtime();
+ final SparseArray<ArrayMap<String, U>> map = mPkgEvents.getMap();
+ for (int i = map.size() - 1; i >= 0; i--) {
+ final int uid = map.keyAt(i);
+ final ArrayMap<String, U> val = map.valueAt(i);
+ for (int j = val.size() - 1; j >= 0; j--) {
+ final String packageName = val.keyAt(j);
+ final U events = val.valueAt(j);
+ dumpEventHeaderLocked(pw, prefix, packageName, uid, events, policy);
+ dumpEventLocked(pw, prefix, events, now);
+ }
+ }
+ }
+ policy.dump(pw, prefix);
+ }
+
+ @GuardedBy("mLock")
+ void dumpEventHeaderLocked(PrintWriter pw, String prefix, String packageName, int uid, U events,
+ T policy) {
+ pw.print(prefix);
+ pw.print("* ");
+ pw.print(packageName);
+ pw.print('/');
+ pw.print(UserHandle.formatUid(uid));
+ pw.print(" exemption=");
+ pw.println(policy.getExemptionReasonString(packageName, uid, events.mExemptReason));
+ }
+
+ @GuardedBy("mLock")
+ void dumpEventLocked(PrintWriter pw, String prefix, U events, long now) {
+ events.dump(pw, " " + prefix, now);
+ }
+
+ abstract static class BaseAppStateEventsPolicy<V extends BaseAppStateEventsTracker>
+ extends BaseAppStatePolicy<V> implements MaxTrackingDurationConfig {
+ /**
+ * The key to the maximum duration we'd keep tracking, events earlier than that
+ * will be discarded.
+ */
+ final @NonNull String mKeyMaxTrackingDuration;
+
+ /**
+ * The default to the {@link #mMaxTrackingDuration}.
+ */
+ final long mDefaultMaxTrackingDuration;
+
+ /**
+ * The maximum duration we'd keep tracking, events earlier than that will be discarded.
+ */
+ volatile long mMaxTrackingDuration;
+
+ BaseAppStateEventsPolicy(@NonNull Injector<?> injector, @NonNull V tracker,
+ @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled,
+ @NonNull String keyMaxTrackingDuration, long defaultMaxTrackingDuration) {
+ super(injector, tracker, keyTrackerEnabled, defaultTrackerEnabled);
+ mKeyMaxTrackingDuration = keyMaxTrackingDuration;
+ mDefaultMaxTrackingDuration = defaultMaxTrackingDuration;
+ }
+
+ @Override
+ public void onPropertiesChanged(String name) {
+ if (mKeyMaxTrackingDuration.equals(name)) {
+ updateMaxTrackingDuration();
+ } else {
+ super.onPropertiesChanged(name);
+ }
+ }
+
+ @Override
+ public void onSystemReady() {
+ super.onSystemReady();
+ updateMaxTrackingDuration();
+ }
+
+ /**
+ * Called when the maximum duration we'd keep tracking has been changed.
+ */
+ public abstract void onMaxTrackingDurationChanged(long maxDuration);
+
+ void updateMaxTrackingDuration() {
+ long max = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ mKeyMaxTrackingDuration, mDefaultMaxTrackingDuration);
+ if (max != mMaxTrackingDuration) {
+ mMaxTrackingDuration = max;
+ onMaxTrackingDurationChanged(max);
+ }
+ }
+
+ @Override
+ public long getMaxTrackingDuration() {
+ return mMaxTrackingDuration;
+ }
+
+ String getExemptionReasonString(String packageName, int uid, @ReasonCode int reason) {
+ return PowerExemptionManager.reasonCodeToString(reason);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ super.dump(pw, prefix);
+ if (isEnabled()) {
+ pw.print(prefix);
+ pw.print(mKeyMaxTrackingDuration);
+ pw.print('=');
+ pw.println(mMaxTrackingDuration);
+ }
+ }
+ }
+
+ /**
+ * Simple event table, with only one track of events.
+ */
+ static class SimplePackageEvents extends BaseAppStateTimeEvents {
+ static final int DEFAULT_INDEX = 0;
+
+ SimplePackageEvents(int uid, String packageName,
+ MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, 1, TAG, maxTrackingDurationConfig);
+ mEvents[DEFAULT_INDEX] = new LinkedList<Long>();
+ }
+
+ long getTotalEvents(long now) {
+ return getTotalEvents(now, DEFAULT_INDEX);
+ }
+
+ long getTotalEventsSince(long since, long now) {
+ return getTotalEventsSince(since, now, DEFAULT_INDEX);
+ }
+
+ @Override
+ String formatEventTypeLabel(int index) {
+ return "";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStatePolicy.java b/services/core/java/com/android/server/am/BaseAppStatePolicy.java
new file mode 100644
index 0000000..67318a7
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStatePolicy.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.app.ActivityManager.RestrictionLevel;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.provider.DeviceConfig;
+
+import com.android.server.am.BaseAppStateTracker.Injector;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class to track the policy for certain state of the app.
+ *
+ * @param <T> A class derived from BaseAppStateTracker.
+ */
+public abstract class BaseAppStatePolicy<T extends BaseAppStateTracker> {
+
+ protected final Injector<?> mInjector;
+ protected final T mTracker;
+
+ /**
+ * The key to the device config, on whether or not we should enable the tracker.
+ */
+ protected final @NonNull String mKeyTrackerEnabled;
+
+ /**
+ * The default settings on whether or not we should enable the tracker.
+ */
+ protected final boolean mDefaultTrackerEnabled;
+
+ /**
+ * Whether or not we should enable the tracker.
+ */
+ volatile boolean mTrackerEnabled;
+
+ BaseAppStatePolicy(@NonNull Injector<?> injector, @NonNull T tracker,
+ @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled) {
+ mInjector = injector;
+ mTracker = tracker;
+ mKeyTrackerEnabled = keyTrackerEnabled;
+ mDefaultTrackerEnabled = defaultTrackerEnabled;
+ }
+
+ void updateTrackerEnabled() {
+ final boolean enabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ mKeyTrackerEnabled, mDefaultTrackerEnabled);
+ if (enabled != mTrackerEnabled) {
+ mTrackerEnabled = enabled;
+ onTrackerEnabled(enabled);
+ }
+ }
+
+ /**
+ * Called when the tracker enable flag flips.
+ */
+ public abstract void onTrackerEnabled(boolean enabled);
+
+ /**
+ * Called when a device config property in the activity manager namespace
+ * has changed.
+ */
+ public void onPropertiesChanged(@NonNull String name) {
+ if (mKeyTrackerEnabled.equals(name)) {
+ updateTrackerEnabled();
+ }
+ }
+
+ /**
+ * @return The proposed background restriction policy for the given package/uid.
+ */
+ public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+ return RESTRICTION_LEVEL_UNKNOWN;
+ }
+
+ /**
+ * Called when the system is ready to rock.
+ */
+ public void onSystemReady() {
+ updateTrackerEnabled();
+ }
+
+ /**
+ * @return If this tracker is enabled or not.
+ */
+ public boolean isEnabled() {
+ return mTrackerEnabled;
+ }
+
+ /**
+ * @return If the given UID should be exempted.
+ *
+ * <p>
+ * Note: Call it with caution as it'll try to acquire locks in other services.
+ * </p>
+ */
+ @CallSuper
+ @ReasonCode
+ public int shouldExemptUid(int uid) {
+ return mTracker.mAppRestrictionController.getBackgroundRestrictionExemptionReason(uid);
+ }
+
+ /**
+ * Dump to the given printer writer.
+ */
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.print(mKeyTrackerEnabled);
+ pw.print('=');
+ pw.println(mTrackerEnabled);
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeEvents.java b/services/core/java/com/android/server/am/BaseAppStateTimeEvents.java
new file mode 100644
index 0000000..1eccdf2
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeEvents.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A helper class to track the timestamps of individual events.
+ */
+class BaseAppStateTimeEvents<T extends BaseTimeEvent> extends BaseAppStateEvents<T> {
+
+ BaseAppStateTimeEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+ @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+ }
+
+ BaseAppStateTimeEvents(@NonNull BaseAppStateTimeEvents other) {
+ super(other);
+ }
+
+ @Override
+ LinkedList<T> add(LinkedList<T> durations, LinkedList<T> otherDurations) {
+ if (otherDurations == null || otherDurations.size() == 0) {
+ return durations;
+ }
+ if (durations == null || durations.size() == 0) {
+ return (LinkedList<T>) otherDurations.clone();
+ }
+ final Iterator<T> itl = durations.iterator();
+ final Iterator<T> itr = otherDurations.iterator();
+ T l = itl.next(), r = itr.next();
+ LinkedList<T> dest = new LinkedList<>();
+ for (long lts = l.getTimestamp(), rts = r.getTimestamp();
+ lts != Long.MAX_VALUE || rts != Long.MAX_VALUE;) {
+ if (lts == rts) {
+ dest.add((T) l.clone());
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ } else if (lts < rts) {
+ dest.add((T) l.clone());
+ lts = itl.hasNext() ? (l = itl.next()).getTimestamp() : Long.MAX_VALUE;
+ } else {
+ dest.add((T) r.clone());
+ rts = itr.hasNext() ? (r = itr.next()).getTimestamp() : Long.MAX_VALUE;
+ }
+ }
+ return dest;
+ }
+
+ @Override
+ int getTotalEventsSince(long since, long now, int index) {
+ final LinkedList<T> events = mEvents[index];
+ if (events == null || events.size() == 0) {
+ return 0;
+ }
+ int count = 0;
+ for (T event : events) {
+ if (event.getTimestamp() >= since) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ void trimEvents(long earliest, int index) {
+ final LinkedList<T> events = mEvents[index];
+ if (events == null) {
+ return;
+ }
+ while (events.size() > 0) {
+ final T current = events.peek();
+ if (current.getTimestamp() >= earliest) {
+ return; // All we have are newer than the given timestamp.
+ }
+ events.pop();
+ }
+ }
+
+ /**
+ * A data class encapsulate the individual event data.
+ */
+ static class BaseTimeEvent implements Cloneable {
+ /**
+ * The timestamp this event occurred at.
+ */
+ long mTimestamp;
+
+ BaseTimeEvent(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ BaseTimeEvent(BaseTimeEvent other) {
+ mTimestamp = other.mTimestamp;
+ }
+
+ void trimTo(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ long getTimestamp() {
+ return mTimestamp;
+ }
+
+ @Override
+ public Object clone() {
+ return new BaseTimeEvent(this);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (other.getClass() != BaseTimeEvent.class) {
+ return false;
+ }
+ return ((BaseTimeEvent) other).mTimestamp == mTimestamp;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(mTimestamp);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java
new file mode 100644
index 0000000..0c43a33
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEvents.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Base class to track certain individual event of app states, it groups the events into time-based
+ * slots, thus we could only track the total number of events in a slot, eliminating
+ * the needs to track the timestamps for each individual event. This will be much more memory
+ * efficient for the case of massive amount of events.
+ */
+class BaseAppStateTimeSlotEvents extends BaseAppStateEvents<Integer> {
+
+ static final boolean DEBUG_BASE_APP_TIME_SLOT_EVENTS = false;
+
+ /**
+ * The size (in ms) of the timeslot, should be greater than 0 always.
+ */
+ final long mTimeSlotSize;
+
+ /**
+ * The start timestamp of current timeslot.
+ */
+ long[] mCurSlotStartTime;
+
+ BaseAppStateTimeSlotEvents(int uid, @NonNull String packageName, int numOfEventTypes,
+ long timeslotSize, @NonNull String tag,
+ @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, numOfEventTypes, tag, maxTrackingDurationConfig);
+ mTimeSlotSize = timeslotSize;
+ mCurSlotStartTime = new long[numOfEventTypes];
+ }
+
+ BaseAppStateTimeSlotEvents(@NonNull BaseAppStateTimeSlotEvents other) {
+ super(other);
+ mTimeSlotSize = other.mTimeSlotSize;
+ mCurSlotStartTime = new long[other.mCurSlotStartTime.length];
+ for (int i = 0; i < mCurSlotStartTime.length; i++) {
+ mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+ }
+ }
+
+ @Override
+ LinkedList<Integer> add(LinkedList<Integer> events, LinkedList<Integer> otherEvents) {
+ if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+ Slog.wtf(mTag, "Called into BaseAppStateTimeSlotEvents#add unexpected.");
+ }
+ // This function is invalid semantically here without the information of time-bases.
+ return null;
+ }
+
+ @Override
+ void add(BaseAppStateEvents otherObj) {
+ if (otherObj == null || !(otherObj instanceof BaseAppStateTimeSlotEvents)) {
+ return;
+ }
+ final BaseAppStateTimeSlotEvents other = (BaseAppStateTimeSlotEvents) otherObj;
+ if (mEvents.length != other.mEvents.length) {
+ if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+ Slog.wtf(mTag, "Incompatible event table this=" + this + ", other=" + other);
+ }
+ return;
+ }
+ for (int i = 0; i < mEvents.length; i++) {
+ final LinkedList<Integer> otherEvents = other.mEvents[i];
+ if (otherEvents == null || otherEvents.size() == 0) {
+ continue;
+ }
+ LinkedList<Integer> events = mEvents[i];
+ if (events == null || events.size() == 0) {
+ mEvents[i] = new LinkedList<Integer>(otherEvents);
+ mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+ continue;
+ }
+
+ final LinkedList<Integer> dest = new LinkedList<>();
+ final Iterator<Integer> itl = events.iterator();
+ final Iterator<Integer> itr = otherEvents.iterator();
+ final long maxl = mCurSlotStartTime[i];
+ final long maxr = other.mCurSlotStartTime[i];
+ final long minl = maxl - mTimeSlotSize * (events.size() - 1);
+ final long minr = maxr - mTimeSlotSize * (otherEvents.size() - 1);
+ final long latest = Math.max(maxl, maxr);
+ final long earliest = Math.min(minl, minr);
+ for (long start = earliest; start <= latest; start += mTimeSlotSize) {
+ dest.add((start >= minl && start <= maxl ? itl.next() : 0)
+ + (start >= minr && start <= maxr ? itr.next() : 0));
+ }
+ mEvents[i] = dest;
+ if (maxl < maxr) {
+ mCurSlotStartTime[i] = other.mCurSlotStartTime[i];
+ }
+ trimEvents(getEarliest(mCurSlotStartTime[i]), i);
+ }
+ }
+
+ @Override
+ int getTotalEventsSince(long since, long now, int index) {
+ final LinkedList<Integer> events = mEvents[index];
+ if (events == null || events.size() == 0) {
+ return 0;
+ }
+ final long start = getSlotStartTime(since);
+ if (start > mCurSlotStartTime[index]) {
+ return 0;
+ }
+ final long end = Math.min(getSlotStartTime(now), mCurSlotStartTime[index]);
+ final Iterator<Integer> it = events.descendingIterator();
+ int count = 0;
+ for (long time = mCurSlotStartTime[index]; time >= start && it.hasNext();
+ time -= mTimeSlotSize) {
+ final int val = it.next();
+ if (time <= end) {
+ count += val;
+ }
+ }
+ return count;
+ }
+
+ void addEvent(long now, int index) {
+ final long slot = getSlotStartTime(now);
+ if (DEBUG_BASE_APP_TIME_SLOT_EVENTS) {
+ Slog.i(mTag, "Adding event to slot " + slot);
+ }
+ LinkedList<Integer> events = mEvents[index];
+ if (events == null) {
+ events = new LinkedList<Integer>();
+ mEvents[index] = events;
+ }
+ if (events.size() == 0) {
+ events.add(1);
+ } else {
+ for (long start = mCurSlotStartTime[index]; start < slot; start += mTimeSlotSize) {
+ events.add(0);
+ }
+ events.offerLast(events.pollLast() + 1);
+ }
+ mCurSlotStartTime[index] = slot;
+ trimEvents(getEarliest(now), index);
+ }
+
+ @Override
+ void trimEvents(long earliest, int index) {
+ final LinkedList<Integer> events = mEvents[index];
+ if (events == null || events.size() == 0) {
+ return;
+ }
+ final long slot = getSlotStartTime(earliest);
+ for (long time = mCurSlotStartTime[index] - mTimeSlotSize * (events.size() - 1);
+ time < slot && events.size() > 0; time += mTimeSlotSize) {
+ events.pop();
+ }
+ }
+
+ long getSlotStartTime(long timestamp) {
+ return timestamp - timestamp % mTimeSlotSize;
+ }
+
+ @VisibleForTesting
+ long getCurrentSlotStartTime(int index) {
+ return mCurSlotStartTime[index];
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
new file mode 100644
index 0000000..2fbca1f
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_FGS;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
+import static android.os.PowerExemptionManager.reasonCodeToString;
+
+import static com.android.server.am.BaseAppStateTracker.ONE_MINUTE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RestrictionLevel;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.BaseAppStateTimeSlotEventsPolicy;
+import com.android.server.am.BaseAppStateTimeSlotEventsTracker.SimpleAppStateTimeslotEvents;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+/**
+ * Base class to track {@link #BaseAppStateTimeSlotEvents}.
+ */
+abstract class BaseAppStateTimeSlotEventsTracker
+ <T extends BaseAppStateTimeSlotEventsPolicy, U extends SimpleAppStateTimeslotEvents>
+ extends BaseAppStateEventsTracker<T, U> {
+ static final String TAG = "BaseAppStateTimeSlotEventsTracker";
+
+ static final boolean DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER = false;
+
+ // Unlocked since it's only accessed in single thread.
+ private final ArrayMap<U, Integer> mTmpPkgs = new ArrayMap<>();
+
+ private H mHandler;
+
+ BaseAppStateTimeSlotEventsTracker(Context context, AppRestrictionController controller,
+ Constructor<? extends Injector<T>> injector, Object outerContext) {
+ super(context, controller, injector, outerContext);
+ mHandler = new H(this);
+ }
+
+ void onNewEvent(String packageName, int uid) {
+ mHandler.obtainMessage(H.MSG_NEW_EVENT, uid, 0, packageName).sendToTarget();
+ }
+
+ void handleNewEvent(String packageName, int uid) {
+ if (mInjector.getPolicy().shouldExempt(packageName, uid) != REASON_DENIED) {
+ return;
+ }
+ final long now = SystemClock.elapsedRealtime();
+ boolean notify = false;
+ int totalEvents;
+ synchronized (mLock) {
+ U pkgEvents = mPkgEvents.get(uid, packageName);
+ if (pkgEvents == null) {
+ pkgEvents = createAppStateEvents(uid, packageName);
+ mPkgEvents.put(uid, packageName, pkgEvents);
+ }
+ pkgEvents.addEvent(now, SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+ totalEvents = pkgEvents.getTotalEvents(now, SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+ notify = totalEvents >= mInjector.getPolicy().getNumOfEventsThreshold();
+ }
+ if (notify) {
+ mInjector.getPolicy().onExcessiveEvents(
+ packageName, uid, totalEvents, now);
+ }
+ }
+
+ void onMonitorEnabled(boolean enabled) {
+ if (!enabled) {
+ synchronized (mLock) {
+ mPkgEvents.clear();
+ }
+ }
+ }
+
+ void onNumOfEventsThresholdChanged(int threshold) {
+ final long now = SystemClock.elapsedRealtime();
+ synchronized (mLock) {
+ SparseArray<ArrayMap<String, U>> pkgEvents = mPkgEvents.getMap();
+ for (int i = pkgEvents.size() - 1; i >= 0; i--) {
+ final ArrayMap<String, U> pkgs = pkgEvents.valueAt(i);
+ for (int j = pkgs.size() - 1; j >= 0; j--) {
+ final U pkg = pkgs.valueAt(j);
+ int totalEvents = pkg.getTotalEvents(now,
+ SimpleAppStateTimeslotEvents.DEFAULT_INDEX);
+ if (totalEvents >= threshold) {
+ mTmpPkgs.put(pkg, totalEvents);
+ }
+ }
+ }
+ }
+ for (int i = mTmpPkgs.size() - 1; i >= 0; i--) {
+ final U pkg = mTmpPkgs.keyAt(i);
+ mInjector.getPolicy().onExcessiveEvents(
+ pkg.mPackageName, pkg.mUid, mTmpPkgs.valueAt(i), now);
+ }
+ mTmpPkgs.clear();
+ }
+
+ private void trimEvents() {
+ final long now = SystemClock.elapsedRealtime();
+ trim(Math.max(0, now - mInjector.getPolicy().getMaxTrackingDuration()));
+ }
+
+ @Override
+ void onUserInteractionStarted(String packageName, int uid) {
+ mInjector.getPolicy().onUserInteractionStarted(packageName, uid);
+ }
+
+ static class H extends Handler {
+ static final int MSG_NEW_EVENT = 0;
+
+ final BaseAppStateTimeSlotEventsTracker mTracker;
+
+ H(BaseAppStateTimeSlotEventsTracker tracker) {
+ super(tracker.mBgHandler.getLooper());
+ mTracker = tracker;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_NEW_EVENT:
+ mTracker.handleNewEvent((String) msg.obj, msg.arg1);
+ break;
+ }
+ }
+ }
+
+ static class BaseAppStateTimeSlotEventsPolicy<E extends BaseAppStateTimeSlotEventsTracker>
+ extends BaseAppStateEventsPolicy<E> {
+
+ final String mKeyNumOfEventsThreshold;
+ final int mDefaultNumOfEventsThreshold;
+
+ @NonNull
+ private final Object mLock;
+
+ @GuardedBy("mLock")
+ private final ProcessMap<Long> mExcessiveEventPkgs = new ProcessMap<>();
+
+ long mTimeSlotSize = DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER
+ ? SimpleAppStateTimeslotEvents.DEFAULT_TIME_SLOT_SIZE_DEBUG
+ : SimpleAppStateTimeslotEvents.DEFAULT_TIME_SLOT_SIZE;
+
+ volatile int mNumOfEventsThreshold;
+
+ BaseAppStateTimeSlotEventsPolicy(@NonNull Injector injector, @NonNull E tracker,
+ @NonNull String keyTrackerEnabled, boolean defaultTrackerEnabled,
+ @NonNull String keyMaxTrackingDuration, long defaultMaxTrackingDuration,
+ @NonNull String keyNumOfEventsThreshold, int defaultNumOfEventsThreshold) {
+ super(injector, tracker, keyTrackerEnabled, defaultTrackerEnabled,
+ keyMaxTrackingDuration, defaultMaxTrackingDuration);
+ mKeyNumOfEventsThreshold = keyNumOfEventsThreshold;
+ mDefaultNumOfEventsThreshold = defaultNumOfEventsThreshold;
+ mLock = tracker.mLock;
+ }
+
+ @Override
+ public void onSystemReady() {
+ super.onSystemReady();
+ updateNumOfEventsThreshold();
+ }
+
+ @Override
+ public void onPropertiesChanged(String name) {
+ if (mKeyNumOfEventsThreshold.equals(name)) {
+ updateNumOfEventsThreshold();
+ } else {
+ super.onPropertiesChanged(name);
+ }
+ }
+
+ @Override
+ public void onTrackerEnabled(boolean enabled) {
+ mTracker.onMonitorEnabled(enabled);
+ }
+
+ @Override
+ public void onMaxTrackingDurationChanged(long maxDuration) {
+ mTracker.mBgHandler.post(mTracker::trimEvents);
+ }
+
+ private void updateNumOfEventsThreshold() {
+ final int threshold = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ mKeyNumOfEventsThreshold,
+ mDefaultNumOfEventsThreshold);
+ if (threshold != mNumOfEventsThreshold) {
+ mNumOfEventsThreshold = threshold;
+ mTracker.onNumOfEventsThresholdChanged(threshold);
+ }
+ }
+
+ int getNumOfEventsThreshold() {
+ return mNumOfEventsThreshold;
+ }
+
+ long getTimeSlotSize() {
+ return mTimeSlotSize;
+ }
+
+ @VisibleForTesting
+ void setTimeSlotSize(long size) {
+ mTimeSlotSize = size;
+ }
+
+ String getEventName() {
+ return "event";
+ }
+
+ void onExcessiveEvents(String packageName, int uid, int numOfEvents, long now) {
+ boolean notifyController = false;
+ synchronized (mLock) {
+ Long ts = mExcessiveEventPkgs.get(packageName, uid);
+ if (ts == null) {
+ if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+ Slog.i(TAG, "Excessive amount of " + getEventName() + " from "
+ + packageName + "/" + UserHandle.formatUid(uid) + ": " + numOfEvents
+ + " over " + TimeUtils.formatDuration(getMaxTrackingDuration()));
+ }
+ mExcessiveEventPkgs.put(packageName, uid, now);
+ notifyController = true;
+ }
+ }
+ if (notifyController) {
+ mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(
+ uid, REASON_MAIN_FORCED_BY_SYSTEM,
+ REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, true);
+ }
+ }
+
+ /**
+ * Whether or not we should ignore the incoming event.
+ */
+ @ReasonCode int shouldExempt(String packageName, int uid) {
+ if (mTracker.isUidOnTop(uid)) {
+ if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+ Slog.i(TAG, "Ignoring event from " + packageName + "/"
+ + UserHandle.formatUid(uid) + ": top");
+ }
+ return REASON_PROC_STATE_TOP;
+ }
+ if (mTracker.mAppRestrictionController.hasForegroundServices(packageName, uid)) {
+ if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+ Slog.i(TAG, "Ignoring event " + packageName + "/"
+ + UserHandle.formatUid(uid) + ": has active FGS");
+ }
+ return REASON_PROC_STATE_FGS;
+ }
+ final @ReasonCode int reason = shouldExemptUid(uid);
+ if (reason != REASON_DENIED) {
+ if (DEBUG_APP_STATE_TIME_SLOT_EVENT_TRACKER) {
+ Slog.i(TAG, "Ignoring event " + packageName + "/" + UserHandle.formatUid(uid)
+ + ": " + reasonCodeToString(reason));
+ }
+ return reason;
+ }
+ return REASON_DENIED;
+ }
+
+ @Override
+ public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) {
+ synchronized (mLock) {
+ return mExcessiveEventPkgs.get(packageName, uid) == null
+ ? RESTRICTION_LEVEL_ADAPTIVE_BUCKET
+ : RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+ }
+ }
+
+ void onUserInteractionStarted(String packageName, int uid) {
+ boolean notifyController = false;
+ synchronized (mLock) {
+ notifyController = mExcessiveEventPkgs.remove(packageName, uid) != null;
+ }
+ mTracker.mAppRestrictionController.refreshAppRestrictionLevelForUid(uid,
+ REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION, true);
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix) {
+ super.dump(pw, prefix);
+ if (isEnabled()) {
+ pw.print(prefix);
+ pw.print(mKeyNumOfEventsThreshold);
+ pw.print('=');
+ pw.println(mDefaultNumOfEventsThreshold);
+ }
+ pw.print(prefix);
+ pw.print("event_time_slot_size=");
+ pw.println(getTimeSlotSize());
+ }
+ }
+
+ /**
+ * A simple time-slot based event table, with only one track of events.
+ */
+ static class SimpleAppStateTimeslotEvents extends BaseAppStateTimeSlotEvents {
+ static final int DEFAULT_INDEX = 0;
+ static final long DEFAULT_TIME_SLOT_SIZE = 15 * ONE_MINUTE;
+ static final long DEFAULT_TIME_SLOT_SIZE_DEBUG = ONE_MINUTE;
+
+ SimpleAppStateTimeslotEvents(int uid, @NonNull String packageName,
+ long timeslotSize, @NonNull String tag,
+ @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) {
+ super(uid, packageName, 1, timeslotSize, tag, maxTrackingDurationConfig);
+ }
+
+ SimpleAppStateTimeslotEvents(SimpleAppStateTimeslotEvents other) {
+ super(other);
+ }
+
+ @Override
+ String formatEventTypeLabel(int index) {
+ return "";
+ }
+
+ @Override
+ String formatEventSummary(long now, int index) {
+ if (mEvents[DEFAULT_INDEX] == null || mEvents[DEFAULT_INDEX].size() == 0) {
+ return "(none)";
+ }
+ final int total = getTotalEvents(now, DEFAULT_INDEX);
+ return "total=" + total + ", latest="
+ + getTotalEventsSince(mCurSlotStartTime[DEFAULT_INDEX], now, DEFAULT_INDEX)
+ + "(slot=" + TimeUtils.formatTime(mCurSlotStartTime[DEFAULT_INDEX], now) + ")";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
new file mode 100644
index 0000000..2846f6c
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActiveServices.SERVICE_START_FOREGROUND_TIMEOUT;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.session.MediaSessionManager;
+import android.os.BatteryManagerInternal;
+import android.os.BatteryStatsInternal;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+
+/**
+ * Base class to track certain state of the app, could be used to determine the restriction level.
+ *
+ * @param <T> A class derived from BaseAppStatePolicy.
+ */
+public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
+ protected static final String TAG = TAG_WITH_CLASS_NAME ? "BaseAppStatePolicy" : TAG_AM;
+
+ static final long ONE_MINUTE = 60 * 1_000L;
+ static final long ONE_HOUR = 60 * ONE_MINUTE;
+ static final long ONE_DAY = 24 * ONE_HOUR;
+
+ protected final AppRestrictionController mAppRestrictionController;
+ protected final Injector<T> mInjector;
+ protected final Context mContext;
+ protected final Handler mBgHandler;
+ protected final Object mLock;
+
+ BaseAppStateTracker(Context context, AppRestrictionController controller,
+ @Nullable Constructor<? extends Injector<T>> injector, Object outerContext) {
+ mContext = context;
+ mAppRestrictionController = controller;
+ mBgHandler = controller.getBackgroundHandler();
+ mLock = controller.getLock();
+ if (injector == null) {
+ mInjector = new Injector<>();
+ } else {
+ Injector<T> localInjector = null;
+ try {
+ localInjector = injector.newInstance(outerContext);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to instantiate " + injector, e);
+ }
+ mInjector = (localInjector == null) ? new Injector<>() : localInjector;
+ }
+ }
+
+ /**
+ * Return the policy holder of this tracker.
+ */
+ T getPolicy() {
+ return mInjector.getPolicy();
+ }
+
+ /**
+ * Called when the system is ready to rock.
+ */
+ void onSystemReady() {
+ mInjector.onSystemReady();
+ }
+
+ /**
+ * Called when a user with the given uid is added.
+ */
+ void onUidAdded(final int uid) {
+ }
+
+ /**
+ * Called when a user with the given uid is removed.
+ */
+ void onUidRemoved(final int uid) {
+ }
+
+ /**
+ * Called when a user with the given userId is added.
+ */
+ void onUserAdded(final @UserIdInt int userId) {
+ }
+
+ /**
+ * Called when a user with the given userId is started.
+ */
+ void onUserStarted(final @UserIdInt int userId) {
+ }
+
+ /**
+ * Called when a user with the given userId is stopped.
+ */
+ void onUserStopped(final @UserIdInt int userId) {
+ }
+
+ /**
+ * Called when a user with the given userId is removed.
+ */
+ void onUserRemoved(final @UserIdInt int userId) {
+ }
+
+ /**
+ * Called when a device config property in the activity manager namespace
+ * has changed.
+ */
+ void onPropertiesChanged(@NonNull String name) {
+ getPolicy().onPropertiesChanged(name);
+ }
+
+ /**
+ * Called when an app has transitioned into an active state due to user interaction.
+ */
+ void onUserInteractionStarted(String packageName, int uid) {
+ }
+
+ /**
+ * Called when the background restriction settings of the given app is changed.
+ */
+ void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
+ }
+
+ /**
+ * Called when the process state of the given UID has been changed.
+ *
+ * <p>Note: as of now, for simplification, we're tracking the TOP state changes only.</p>
+ */
+ void onUidProcStateChanged(int uid, int procState) {
+ }
+
+ /**
+ * Called when all the processes in the given UID have died.
+ */
+ void onUidGone(int uid) {
+ }
+
+ /**
+ * Dump to the given printer writer.
+ */
+ void dump(PrintWriter pw, String prefix) {
+ mInjector.getPolicy().dump(pw, " " + prefix);
+ }
+
+ static class Injector<T extends BaseAppStatePolicy> {
+ T mAppStatePolicy;
+
+ ActivityManagerInternal mActivityManagerInternal;
+ BatteryManagerInternal mBatteryManagerInternal;
+ BatteryStatsInternal mBatteryStatsInternal;
+ DeviceIdleInternal mDeviceIdleInternal;
+ UserManagerInternal mUserManagerInternal;
+ PackageManager mPackageManager;
+ PermissionManagerServiceInternal mPermissionManagerServiceInternal;
+ AppOpsManager mAppOpsManager;
+ MediaSessionManager mMediaSessionManager;
+ RoleManager mRoleManager;
+
+ void setPolicy(T policy) {
+ mAppStatePolicy = policy;
+ }
+
+ void onSystemReady() {
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
+ mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class);
+ mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mPermissionManagerServiceInternal = LocalServices.getService(
+ PermissionManagerServiceInternal.class);
+ final Context context = mAppStatePolicy.mTracker.mContext;
+ mPackageManager = context.getPackageManager();
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+ mRoleManager = context.getSystemService(RoleManager.class);
+
+ getPolicy().onSystemReady();
+ }
+
+ ActivityManagerInternal getActivityManagerInternal() {
+ return mActivityManagerInternal;
+ }
+
+ BatteryManagerInternal getBatteryManagerInternal() {
+ return mBatteryManagerInternal;
+ }
+
+ BatteryStatsInternal getBatteryStatsInternal() {
+ return mBatteryStatsInternal;
+ }
+
+ T getPolicy() {
+ return mAppStatePolicy;
+ }
+
+ DeviceIdleInternal getDeviceIdleInternal() {
+ return mDeviceIdleInternal;
+ }
+
+ UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
+ /**
+ * Equivalent to {@link java.lang.System#currentTimeMillis}.
+ */
+ long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ PermissionManagerServiceInternal getPermissionManagerServiceInternal() {
+ return mPermissionManagerServiceInternal;
+ }
+
+ AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+
+ MediaSessionManager getMediaSessionManager() {
+ return mMediaSessionManager;
+ }
+
+ long getServiceStartForegroundTimeout() {
+ return SERVICE_START_FOREGROUND_TIMEOUT;
+ }
+
+ RoleManager getRoleManager() {
+ return mRoleManager;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0b92954..c8ad0e8 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -431,6 +431,11 @@
}
@Override
+ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+ return BatteryStatsService.this.getBatteryUsageStats(queries);
+ }
+
+ @Override
public void noteJobsDeferred(int uid, int numDeferred, long sinceLast) {
if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ec6b782..0c383eb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
@@ -46,6 +47,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
@@ -320,7 +322,11 @@
final ProcessReceiverRecord prr = app.mReceivers;
prr.addCurReceiver(r);
app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- mService.updateLruProcessLocked(app, false, null);
+ // Don't bump its LRU position if it's in the background restricted.
+ if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
+ < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ mService.updateLruProcessLocked(app, false, null);
+ }
// Make sure the oom adj score is updated before delivering the broadcast.
// Force an update, even if there are other pending requests, overall it still saves time,
// because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
@@ -2066,6 +2072,13 @@
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();
+ }
+
// Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
// So don't change the incoming record directly.
final BroadcastRecord historyRecord = original.maybeStripForHistory();
diff --git a/services/core/java/com/android/server/am/UidProcessMap.java b/services/core/java/com/android/server/am/UidProcessMap.java
new file mode 100644
index 0000000..f708d37
--- /dev/null
+++ b/services/core/java/com/android/server/am/UidProcessMap.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+/**
+ * Utility class to track mappings between (UID, name) and E.
+ *
+ * @param <E> The type of the values in this map.
+ */
+public class UidProcessMap<E> {
+ final SparseArray<ArrayMap<String, E>> mMap = new SparseArray<>();
+
+ /**
+ * Retrieve a value from the map.
+ */
+ public E get(int uid, String name) {
+ final ArrayMap<String, E> names = mMap.get(uid);
+ if (names == null) {
+ return null;
+ }
+ return names.get(name);
+ }
+
+ /**
+ * Add a new value to the array map.
+ */
+ public E put(int uid, String name, E value) {
+ ArrayMap<String, E> names = mMap.get(uid);
+ if (names == null) {
+ names = new ArrayMap<String, E>(2);
+ mMap.put(uid, names);
+ }
+ names.put(name, value);
+ return value;
+ }
+
+ /**
+ * Remove an existing key (uid, name) from the array map.
+ */
+ public E remove(int uid, String name) {
+ final int index = mMap.indexOfKey(uid);
+ if (index < 0) {
+ return null;
+ }
+ final ArrayMap<String, E> names = mMap.valueAt(index);
+ if (names != null) {
+ final E old = names.remove(name);
+ if (names.isEmpty()) {
+ mMap.removeAt(index);
+ }
+ return old;
+ }
+ return null;
+ }
+
+ /**
+ * Return the underneath map.
+ */
+ public SparseArray<ArrayMap<String, E>> getMap() {
+ return mMap;
+ }
+
+ /**
+ * Return the number of items in this map.
+ */
+ public int size() {
+ return mMap.size();
+ }
+
+ /**
+ * Make the map empty. All storage is released.
+ */
+ public void clear() {
+ mMap.clear();
+ }
+
+ /**
+ * Perform a {@link #put} of all key/value pairs in other.
+ */
+ public void putAll(UidProcessMap<E> other) {
+ for (int i = other.mMap.size() - 1; i >= 0; i--) {
+ final int uid = other.mMap.keyAt(i);
+ final ArrayMap<String, E> names = mMap.get(uid);
+ if (names != null) {
+ names.putAll(other.mMap.valueAt(i));
+ } else {
+ mMap.put(uid, new ArrayMap<String, E>(other.mMap.valueAt(i)));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 417ebcd..f1429a5 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -83,7 +83,6 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
-import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
import java.io.FileDescriptor;
import java.util.List;
@@ -128,6 +127,8 @@
private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
@GuardedBy("mOverrideConfigLock")
private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
+ @Nullable
+ private final GameServiceController mGameServiceController;
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
@@ -140,6 +141,16 @@
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+ mGameServiceController = new GameServiceController(
+ BackgroundThread.getExecutor(),
+ new GameServiceProviderSelectorImpl(
+ context.getResources(),
+ context.getPackageManager()),
+ new GameServiceProviderInstanceFactoryImpl(context));
+ } else {
+ mGameServiceController = null;
+ }
}
@Override
@@ -610,8 +621,6 @@
*/
public static class Lifecycle extends SystemService {
private GameManagerService mService;
- @Nullable
- private GameServiceController mGameServiceController;
public Lifecycle(Context context) {
super(context);
@@ -624,57 +633,33 @@
publishBinderService(Context.GAME_SERVICE, mService);
mService.registerDeviceConfigListener();
mService.registerPackageReceiver();
-
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
- mGameServiceController = new GameServiceController(
- BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- getContext().getResources(),
- getContext().getPackageManager()),
- new GameServiceProviderInstanceFactoryImpl(getContext()));
- }
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
mService.onBootCompleted();
- if (mGameServiceController != null) {
- mGameServiceController.onBootComplete();
- }
}
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
- mService.onUserStarting(user.getUserIdentifier());
- if (mGameServiceController != null) {
- mGameServiceController.notifyUserStarted(user);
- }
+ mService.onUserStarting(user);
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- super.onUserUnlocking(user);
- if (mGameServiceController != null) {
- mGameServiceController.notifyUserUnlocking(user);
- }
+ mService.onUserUnlocking(user);
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
- mService.onUserStopping(user.getUserIdentifier());
- if (mGameServiceController != null) {
- mGameServiceController.notifyUserStopped(user);
- }
+ mService.onUserStopping(user);
}
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
- mService.onUserSwitching(from, to.getUserIdentifier());
- if (mGameServiceController != null) {
- mGameServiceController.notifyNewForegroundUser(to);
- }
+ mService.onUserSwitching(from, to);
}
}
@@ -868,14 +853,38 @@
}
/**
+ * Sets the game service provider to a given package, meant for testing.
+ *
+ * <p>This setting persists until the next call or until the next reboot.
+ *
+ * <p>Checks that the caller has {@link android.Manifest.permission#SET_GAME_SERVICE}.
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.SET_GAME_SERVICE)
+ public void setGameServiceProvider(@Nullable String packageName) throws SecurityException {
+ checkPermission(Manifest.permission.SET_GAME_SERVICE);
+
+ if (mGameServiceController == null) {
+ return;
+ }
+
+ mGameServiceController.setGameServiceProvider(packageName);
+ }
+
+ /**
* Notified when boot is completed.
*/
@VisibleForTesting
void onBootCompleted() {
Slog.d(TAG, "onBootCompleted");
+ if (mGameServiceController != null) {
+ mGameServiceController.onBootComplete();
+ }
}
- void onUserStarting(int userId) {
+ void onUserStarting(@NonNull TargetUser user) {
+ final int userId = user.getUserIdentifier();
+
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
GameManagerSettings userSettings =
@@ -887,9 +896,21 @@
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = userId;
mHandler.sendMessage(msg);
+
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyUserStarted(user);
+ }
}
- void onUserStopping(int userId) {
+ void onUserUnlocking(@NonNull TargetUser user) {
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyUserUnlocking(user);
+ }
+ }
+
+ void onUserStopping(TargetUser user) {
+ final int userId = user.getUserIdentifier();
+
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return;
@@ -898,9 +919,14 @@
msg.obj = userId;
mHandler.sendMessage(msg);
}
+
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyUserStopped(user);
+ }
}
- void onUserSwitching(TargetUser from, int toUserId) {
+ void onUserSwitching(TargetUser from, TargetUser to) {
+ final int toUserId = to.getUserIdentifier();
if (from != null) {
synchronized (mLock) {
final int fromUserId = from.getUserIdentifier();
@@ -914,6 +940,10 @@
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = toUserId;
mHandler.sendMessage(msg);
+
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyNewForegroundUser(to);
+ }
}
/**
diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java
index ac720b9..397439a 100644
--- a/services/core/java/com/android/server/app/GameServiceController.java
+++ b/services/core/java/com/android/server/app/GameServiceController.java
@@ -44,6 +44,8 @@
private volatile boolean mHasBootCompleted;
@Nullable
+ private volatile String mGameServiceProviderOverride;
+ @Nullable
private volatile SystemService.TargetUser mCurrentForegroundUser;
@GuardedBy("mLock")
@Nullable
@@ -108,6 +110,16 @@
setCurrentForegroundUserAndEvaluateProvider(null);
}
+ void setGameServiceProvider(@Nullable String packageName) {
+ boolean hasPackageChanged = !Objects.equals(mGameServiceProviderOverride, packageName);
+ if (!hasPackageChanged) {
+ return;
+ }
+ mGameServiceProviderOverride = packageName;
+
+ mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider);
+ }
+
private void setCurrentForegroundUserAndEvaluateProvider(
@Nullable SystemService.TargetUser user) {
boolean hasUserChanged =
@@ -128,7 +140,8 @@
synchronized (mLock) {
GameServiceProviderConfiguration selectedGameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(mCurrentForegroundUser);
+ mGameServiceProviderSelector.get(mCurrentForegroundUser,
+ mGameServiceProviderOverride);
boolean didActiveGameServiceProviderChanged =
!Objects.equals(selectedGameServiceProviderConfiguration,
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 8578de7..9136219 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -330,7 +330,6 @@
+ ") is not awaiting game session request. Ignoring.");
return;
}
- mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested());
GameSessionViewHostConfiguration gameSessionViewHostConfiguration =
createViewHostConfigurationForTask(taskId);
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelector.java b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
index 51d3515..0f55b9f 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderSelector.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
@@ -30,5 +30,6 @@
* Service provider for the given user or {@code null} if none should be used.
*/
@Nullable
- GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user);
+ GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user,
+ @Nullable String packageNameOverride);
}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
index 54ef707..c1ad668 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
@@ -57,7 +57,8 @@
@Override
@Nullable
- public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user) {
+ public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user,
+ @Nullable String packageNameOverride) {
if (user == null) {
return null;
}
@@ -68,9 +69,16 @@
return null;
}
- String gameServicePackage =
- mResources.getString(
- com.android.internal.R.string.config_systemGameService);
+ int resolveInfoQueryFlags;
+ String gameServicePackage;
+ if (!TextUtils.isEmpty(packageNameOverride)) {
+ resolveInfoQueryFlags = 0;
+ gameServicePackage = packageNameOverride;
+ } else {
+ resolveInfoQueryFlags = PackageManager.MATCH_SYSTEM_ONLY;
+ gameServicePackage = mResources.getString(
+ com.android.internal.R.string.config_systemGameService);
+ }
if (TextUtils.isEmpty(gameServicePackage)) {
Slog.w(TAG, "No game service package defined");
@@ -81,7 +89,7 @@
List<ResolveInfo> gameServiceResolveInfos =
mPackageManager.queryIntentServicesAsUser(
new Intent(GameService.ACTION_GAME_SERVICE).setPackage(gameServicePackage),
- PackageManager.GET_META_DATA | PackageManager.MATCH_SYSTEM_ONLY,
+ PackageManager.GET_META_DATA | resolveInfoQueryFlags,
userId);
if (DEBUG) {
Slog.i(TAG, "Querying package: " + gameServicePackage + " and user id: " + userId);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 8a62c34..4ae1bd37 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1207,7 +1207,6 @@
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
- BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
@@ -1376,6 +1375,9 @@
case AudioSystem.DEVICE_OUT_USB_HEADSET:
connType = AudioRoutesInfo.MAIN_USB;
break;
+ case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET:
+ connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
+ break;
}
synchronized (mCurAudioRoutes) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a853ac5..407d42e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -603,7 +603,6 @@
// Devices for which the volume is fixed (volume is either max or muted)
Set<Integer> mFixedVolumeDevices = new HashSet<>(Arrays.asList(
- AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET,
AudioSystem.DEVICE_OUT_AUX_LINE));
// Devices for which the volume is always max, no volume panel
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 15d6a89..4fc2e22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -207,6 +207,11 @@
public ICancellationSignal detectInteractionWithContext(OperationContext context) {
return detectInteraction();
}
+
+ @Override
+ public void onContextChanged(OperationContext context) {
+ Slog.w(TAG, "onContextChanged");
+ }
};
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 1eb153c..452c972 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -212,6 +212,11 @@
public void onPointerUpWithContext(PointerContext context) {
onPointerUp(context.pointerId);
}
+
+ @Override
+ public void onContextChanged(OperationContext context) {
+ Slog.w(TAG, "onContextChanged");
+ }
};
}
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 5b76695..28508f4 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -654,7 +654,7 @@
final boolean canCopyIntoProfile = !hasRestriction(
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
- setPrimaryClipInternalLocked(
+ setPrimaryClipInternalNoClassifyLocked(
getClipboardLocked(id), clip, uid, sourcePackage);
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index f4c36c6..1b55257 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -412,7 +412,7 @@
if (mLoggingEnabled) {
Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
}
- if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
+ if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) {
mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
mCurrentBrightnessMapper.getShortTermModelTimeout());
} else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b9c7123..de933cc 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3510,6 +3510,16 @@
public void unregisterLidSwitchCallback(LidSwitchCallback callbacks) {
unregisterLidSwitchCallbackInternal(callbacks);
}
+
+ @Override
+ public InputChannel createInputChannel(String inputChannelName) {
+ return InputManagerService.this.createInputChannel(inputChannelName);
+ }
+
+ @Override
+ public void pilferPointers(IBinder token) {
+ nativePilferPointers(mPtr, token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
new file mode 100644
index 0000000..4c26166
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.os.Process;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+
+final class HandwritingEventReceiverSurface {
+
+ public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
+ static final boolean DEBUG = HandwritingModeController.DEBUG;
+
+ private final int mClientPid;
+ private final int mClientUid;
+
+ private final InputApplicationHandle mApplicationHandle;
+ private final InputWindowHandle mWindowHandle;
+ private final InputChannel mClientChannel;
+ private final SurfaceControl mInputSurface;
+ private boolean mIsIntercepting;
+
+ HandwritingEventReceiverSurface(String name, int displayId, @NonNull SurfaceControl sc,
+ @NonNull InputChannel inputChannel) {
+ // Initialized the window as being owned by the system.
+ mClientPid = Process.myPid();
+ mClientUid = Process.myUid();
+ mApplicationHandle = new InputApplicationHandle(null, name,
+ DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
+
+ mClientChannel = inputChannel;
+ mInputSurface = sc;
+
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
+ mWindowHandle.name = name;
+ mWindowHandle.token = mClientChannel.getToken();
+ mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+ mWindowHandle.visible = true;
+ mWindowHandle.focusable = false;
+ mWindowHandle.hasWallpaper = false;
+ mWindowHandle.paused = false;
+ mWindowHandle.ownerPid = mClientPid;
+ mWindowHandle.ownerUid = mClientUid;
+ mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ | WindowManager.LayoutParams.INPUT_FEATURE_INTERCEPTS_STYLUS;
+ mWindowHandle.scaleFactor = 1.0f;
+ mWindowHandle.trustedOverlay = true;
+ mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface as crop */);
+
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setInputWindowInfo(mInputSurface, mWindowHandle);
+ t.setLayer(mInputSurface, Integer.MAX_VALUE);
+ t.setPosition(mInputSurface, 0, 0);
+ // Use an arbitrarily large crop that is positioned at the origin. The crop determines the
+ // bounds and the coordinate space of the input events, so it must start at the origin to
+ // receive input in display space.
+ // TODO(b/210039666): fix this in SurfaceFlinger and avoid the hack.
+ t.setCrop(mInputSurface, new Rect(0, 0, 10000, 10000));
+ t.show(mInputSurface);
+ t.apply();
+
+ mIsIntercepting = false;
+ }
+
+ void startIntercepting() {
+ // TODO(b/210978621): Update the spy window's PID and UID to be associated with the IME so
+ // that ANRs are correctly attributed to the IME.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mWindowHandle.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ t.setInputWindowInfo(mInputSurface, mWindowHandle);
+ t.apply();
+ mIsIntercepting = true;
+ }
+
+ boolean isIntercepting() {
+ return mIsIntercepting;
+ }
+
+ void remove() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.remove(mInputSurface);
+ t.apply();
+ }
+
+ InputChannel getInputChannel() {
+ return mClientChannel;
+ }
+
+ SurfaceControl getSurface() {
+ return mInputSurface;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
new file mode 100644
index 0000000..d31f7c5
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.InputDevice.SOURCE_STYLUS;
+
+import android.annotation.AnyThread;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.hardware.input.InputManagerInternal;
+import android.os.Looper;
+import android.util.Slog;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.OptionalInt;
+
+// TODO(b/210039666): See if we can make this class thread-safe.
+final class HandwritingModeController {
+
+ public static final String TAG = HandwritingModeController.class.getSimpleName();
+ // TODO(b/210039666): flip the flag.
+ static final boolean DEBUG = true;
+ private static final int EVENT_BUFFER_SIZE = 100;
+
+ // This must be the looper for the UiThread.
+ private final Looper mLooper;
+ private final InputManagerInternal mInputManagerInternal;
+ private final WindowManagerInternal mWindowManagerInternal;
+
+ private List<MotionEvent> mHandwritingBuffer;
+ private InputEventReceiver mHandwritingEventReceiver;
+ private Runnable mInkWindowInitRunnable;
+ private boolean mRecordingGesture;
+ private int mCurrentDisplayId;
+
+ private HandwritingEventReceiverSurface mHandwritingSurface;
+
+ private int mCurrentRequestId;
+
+ @AnyThread
+ HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) {
+ mLooper = uiThreadLooper;
+ mCurrentDisplayId = Display.INVALID_DISPLAY;
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mCurrentRequestId = 0;
+ mInkWindowInitRunnable = inkWindowInitRunnable;
+ }
+
+ // TODO(b/210039666): Consider moving this to MotionEvent
+ private static boolean isStylusEvent(MotionEvent event) {
+ if (!event.isFromSource(SOURCE_STYLUS)) {
+ return false;
+ }
+ final int tool = event.getToolType(0);
+ return tool == MotionEvent.TOOL_TYPE_STYLUS || tool == MotionEvent.TOOL_TYPE_ERASER;
+ }
+
+ /**
+ * Initializes the handwriting spy on the given displayId.
+ *
+ * This must be called from the UI Thread because it will start processing events using an
+ * InputEventReceiver that batches events according to the current thread's Choreographer.
+ */
+ @UiThread
+ void initializeHandwritingSpy(int displayId) {
+ // When resetting, reuse resources if we are reinitializing on the same display.
+ reset(displayId == mCurrentDisplayId);
+ mCurrentDisplayId = displayId;
+
+ if (mHandwritingBuffer == null) {
+ mHandwritingBuffer = new ArrayList<>(EVENT_BUFFER_SIZE);
+ }
+
+ if (DEBUG) Slog.d(TAG, "Initializing handwriting spy monitor for display: " + displayId);
+ final String name = "stylus-handwriting-event-receiver-" + displayId;
+ final InputChannel channel = mInputManagerInternal.createInputChannel(name);
+ Objects.requireNonNull(channel, "Failed to create input channel");
+ final SurfaceControl surface =
+ mHandwritingSurface != null ? mHandwritingSurface.getSurface()
+ : mWindowManagerInternal.getHandwritingSurfaceForDisplay(displayId);
+ if (surface == null) {
+ Slog.e(TAG, "Failed to create input surface");
+ return;
+ }
+ mHandwritingSurface =
+ new HandwritingEventReceiverSurface(name, displayId, surface, channel);
+ // Use a dup of the input channel so that event processing can be paused by disposing the
+ // event receiver without causing a fd hangup.
+ mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
+ channel.dup(), mLooper, Choreographer.getInstance(), this::onInputEvent);
+ mCurrentRequestId++;
+ }
+
+ OptionalInt getCurrentRequestId() {
+ if (mHandwritingSurface == null) {
+ Slog.e(TAG, "Cannot get requestId: Handwriting was not initialized.");
+ return OptionalInt.empty();
+ }
+ return OptionalInt.of(mCurrentRequestId);
+ }
+
+ /**
+ * Starts a {@link HandwritingSession} to transfer to the IME.
+ *
+ * This must be called from the UI Thread to avoid race conditions between processing more
+ * input events and disposing the input event receiver.
+ * @return the handwriting session to send to the IME, or null if the request was invalid.
+ */
+ @UiThread
+ @Nullable
+ HandwritingSession startHandwritingSession(int requestId) {
+ if (mHandwritingSurface == null) {
+ Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
+ return null;
+ }
+ if (requestId != mCurrentRequestId) {
+ Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
+ return null;
+ }
+ Objects.requireNonNull(mHandwritingEventReceiver,
+ "Handwriting session was already transferred to IME.");
+ if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
+
+ mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
+
+ // Stop processing more events.
+ mHandwritingEventReceiver.dispose();
+ mHandwritingEventReceiver = null;
+ mRecordingGesture = false;
+
+ if (mHandwritingSurface.isIntercepting()) {
+ throw new IllegalStateException(
+ "Handwriting surface should not be already intercepting.");
+ }
+ mHandwritingSurface.startIntercepting();
+
+ return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
+ mHandwritingBuffer);
+ }
+
+ /**
+ * Reset the current handwriting session without initializing another session.
+ *
+ * This must be called from UI Thread to avoid race conditions between processing more input
+ * events and disposing the input event receiver.
+ */
+ @UiThread
+ void reset() {
+ reset(false /* reinitializing */);
+ }
+
+ private void reset(boolean reinitializing) {
+ if (mHandwritingEventReceiver != null) {
+ mHandwritingEventReceiver.dispose();
+ mHandwritingEventReceiver = null;
+ }
+
+ if (mHandwritingBuffer != null) {
+ mHandwritingBuffer.forEach(MotionEvent::recycle);
+ mHandwritingBuffer.clear();
+ if (!reinitializing) {
+ mHandwritingBuffer = null;
+ }
+ }
+
+ if (mHandwritingSurface != null) {
+ mHandwritingSurface.getInputChannel().dispose();
+ if (!reinitializing) {
+ mHandwritingSurface.remove();
+ mHandwritingSurface = null;
+ }
+ }
+
+ mRecordingGesture = false;
+ }
+
+ private boolean onInputEvent(InputEvent ev) {
+ if (mHandwritingEventReceiver == null) {
+ throw new IllegalStateException(
+ "Input Event should not be processed when IME has the spy channel.");
+ }
+
+ if (!(ev instanceof MotionEvent)) {
+ Slog.e("Stylus", "Received non-motion event in stylus monitor.");
+ return false;
+ }
+ final MotionEvent event = (MotionEvent) ev;
+ if (!isStylusEvent(event)) {
+ return false;
+ }
+
+ onStylusEvent(event);
+ return true;
+ }
+
+ private void onStylusEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+
+ if (mInkWindowInitRunnable != null && (action == MotionEvent.ACTION_HOVER_ENTER
+ || event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) {
+ // Ask IMMS to make ink window ready.
+ mInkWindowInitRunnable.run();
+ mInkWindowInitRunnable = null;
+ }
+
+ if (action == MotionEvent.ACTION_UP) {
+ mRecordingGesture = false;
+ mHandwritingBuffer.clear();
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mRecordingGesture = true;
+ }
+
+ if (!mRecordingGesture) {
+ return;
+ }
+
+ if (mHandwritingBuffer.size() >= EVENT_BUFFER_SIZE) {
+ if (DEBUG) {
+ Slog.w(TAG, "Current gesture exceeds the buffer capacity."
+ + " The rest of the gesture will not be recorded.");
+ }
+ mRecordingGesture = false;
+ return;
+ }
+
+ mHandwritingBuffer.add(MotionEvent.obtain(event));
+ }
+
+ static final class HandwritingSession {
+ private final int mRequestId;
+ private final InputChannel mHandwritingChannel;
+ private final List<MotionEvent> mRecordedEvents;
+
+ private HandwritingSession(int requestId, InputChannel handwritingChannel,
+ List<MotionEvent> recordedEvents) {
+ mRequestId = requestId;
+ mHandwritingChannel = handwritingChannel;
+ mRecordedEvents = recordedEvents;
+ }
+
+ int getRequestId() {
+ return mRequestId;
+ }
+
+ InputChannel getHandwritingChannel() {
+ return mHandwritingChannel;
+ }
+
+ List<MotionEvent> getRecordedEvents() {
+ return mRecordedEvents;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c86ebd2..c87ca92 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -214,9 +214,20 @@
}
@AnyThread
- void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) {
+ boolean startStylusHandwriting(int requestId, InputChannel channel, List<MotionEvent> events) {
try {
- mTarget.startStylusHandwriting(channel, events);
+ mTarget.startStylusHandwriting(requestId, channel, events);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ return false;
+ }
+ return true;
+ }
+
+ @AnyThread
+ void initInkWindow() {
+ try {
+ mTarget.initInkWindow();
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 2230dcd..b814782 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -315,9 +315,10 @@
mService.reRequestCurrentClientSessionLocked();
}
- if (mSupportsStylusHw) {
- // TODO init Handwriting spy.
- }
+ // reset Handwriting event receiver.
+ // always call this as it handles changes in mSupportsStylusHw. It is a noop
+ // if unchanged.
+ mService.scheduleResetStylusHandwriting();
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index feb0d138..c6a206b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -196,6 +196,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -223,6 +224,9 @@
private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
+ private static final int MSG_RESET_HANDWRITING = 1090;
+ private static final int MSG_START_HANDWRITING = 1100;
+
private static final int MSG_UNBIND_CLIENT = 3000;
private static final int MSG_BIND_CLIENT = 3010;
private static final int MSG_SET_ACTIVE = 3020;
@@ -299,12 +303,6 @@
private int mMethodMapUpdateCount = 0;
/**
- * Tracks requestIds for Stylus handwriting mode.
- */
- @GuardedBy("ImfLock.class")
- private int mHwRequestId = 0;
-
- /**
* The display id for which the latest startInput was called.
*/
@GuardedBy("ImfLock.class")
@@ -323,6 +321,8 @@
private final PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
private boolean mNotificationShown;
+ @GuardedBy("ImfLock.class")
+ private final HandwritingModeController mHwController;
static class SessionState {
final ClientState client;
@@ -1636,6 +1636,19 @@
mBindingController = new InputMethodBindingController(this);
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
+ mHwController = new HandwritingModeController(thread.getLooper(),
+ new InkWindowInitializer());
+ }
+
+ private final class InkWindowInitializer implements Runnable {
+ public void run() {
+ synchronized (ImfLock.class) {
+ IInputMethodInvoker curMethod = getCurMethodLocked();
+ if (curMethod != null) {
+ curMethod.initInkWindow();
+ }
+ }
+ }
}
@GuardedBy("ImfLock.class")
@@ -2519,6 +2532,11 @@
}
@AnyThread
+ void scheduleResetStylusHandwriting() {
+ mHandler.obtainMessage(MSG_RESET_HANDWRITING).sendToTarget();
+ }
+
+ @AnyThread
void scheduleNotifyImeUidToAudioService(int uid) {
mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
@@ -3060,6 +3078,7 @@
}
}
+ @BinderThread
@Override
public void startStylusHandwriting(IInputMethodClient client) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
@@ -3074,18 +3093,21 @@
final long ident = Binder.clearCallingIdentity();
try {
if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return;
}
if (!mBindingController.supportsStylusHandwriting()) {
Slog.w(TAG, "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return;
+ }
+ final OptionalInt requestId = mHwController.getCurrentRequestId();
+ if (!requestId.isPresent()) {
+ Slog.e(TAG, "Stylus handwriting was not initialized.");
return;
}
if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
- curMethod.canStartStylusHandwriting(++mHwRequestId);
+ curMethod.canStartStylusHandwriting(requestId.getAsInt());
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4118,6 +4140,18 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ @BinderThread
+ private void finishStylusHandwriting(int requestId) {
+ synchronized (ImfLock.class) {
+ final OptionalInt curRequest = mHwController.getCurrentRequestId();
+ if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
+ Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
+ + requestId);
+ }
+ scheduleResetStylusHandwriting();
+ }
+ }
+
@GuardedBy("ImfLock.class")
private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
if (token == null) {
@@ -4358,21 +4392,47 @@
}
return true;
}
+
+ case MSG_RESET_HANDWRITING: {
+ synchronized (ImfLock.class) {
+ if (mBindingController.supportsStylusHandwriting()
+ && getCurMethodLocked() != null) {
+ mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+ } else {
+ mHwController.reset();
+ }
+ }
+ return true;
+ }
+ case MSG_START_HANDWRITING:
+ synchronized (ImfLock.class) {
+ IInputMethodInvoker curMethod = getCurMethodLocked();
+ if (curMethod == null) {
+ return true;
+ }
+ final HandwritingModeController.HandwritingSession session =
+ mHwController.startHandwritingSession(msg.arg1);
+ if (session == null) {
+ Slog.e(TAG,
+ "Failed to start handwriting session for requestId: " + msg.arg1);
+ return true;
+ }
+
+ if (!curMethod.startStylusHandwriting(session.getRequestId(),
+ session.getHandwritingChannel(), session.getRecordedEvents())) {
+ // When failed to issue IPCs, re-initialize handwriting state.
+ Slog.w(TAG, "Resetting handwriting mode.");
+ mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+ }
+ }
+ return true;
}
return false;
}
@BinderThread
private void onStylusHandwritingReady(int requestId) {
- synchronized (ImfLock.class) {
- if (mHwRequestId != requestId) {
- // obsolete request
- return;
- }
-
- // TODO: replace null with actual Channel, MotionEvents
- getCurMethodLocked().startStylusHandwriting(null, null);
- }
+ mHandler.obtainMessage(MSG_START_HANDWRITING, requestId, 0 /* unused */).sendToTarget();
}
private void handleSetInteractive(final boolean interactive) {
@@ -5917,5 +5977,11 @@
public void onStylusHandwritingReady(int requestId) {
mImms.onStylusHandwritingReady(requestId);
}
+
+ @BinderThread
+ @Override
+ public void finishStylusHandwriting(int requestId) {
+ mImms.finishStylusHandwriting(requestId);
+ }
}
}
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index 57a7620..ac42646 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -83,9 +83,9 @@
}
@Override
- public void onError() {
+ public void onError(Throwable t) {
try {
- listener.onResults("Service not Available", Collections.emptyList());
+ listener.onResults(t.toString(), Collections.emptyList());
} catch (RemoteException e) {
// ignore
}
@@ -110,9 +110,9 @@
}
@Override
- public void onError() {
+ public void onError(Throwable t) {
try {
- listener.onResults("Service not Available", Collections.emptyList());
+ listener.onResults(t.toString(), Collections.emptyList());
} catch (RemoteException e) {
// ignore
}
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 61e6d14..de8e06a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -154,6 +155,10 @@
private boolean mIsWifiScanningEnabled = false;
private boolean mIsWifiMainEnabled = false;
+ // True if BT is available for the Context Hub
+ private boolean mIsBtScanningEnabled = false;
+ private boolean mIsBtMainEnabled = false;
+
// A hashmap used to record if a contexthub is waiting for daily query
private Set<Integer> mMetricQueryPendingContextHubIds =
Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
@@ -333,6 +338,25 @@
}
+ if (mContextHubWrapper.supportsBtSettingNotifications()) {
+ sendBtSettingUpdate(true /* forceUpdate */);
+
+ BroadcastReceiver btReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
+ || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
+ intent.getAction())) {
+ sendBtSettingUpdate(false /* forceUpdate */);
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+ mContext.registerReceiver(btReceiver, filter);
+ }
+
scheduleDailyMetricSnapshot();
}
@@ -735,6 +759,7 @@
sendWifiSettingUpdate(true /* forceUpdate */);
sendAirplaneModeSettingUpdate();
sendMicrophoneDisableSettingUpdateForCurrentUser();
+ sendBtSettingUpdate(true /* forceUpdate */);
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
@@ -1133,6 +1158,39 @@
}
/**
+ * Obtains the latest BT availability setting value and notifies the Context Hub.
+ *
+ * @param forceUpdate True to force send update to the Context Hub, otherwise only send the
+ * update when the BT availability changes.
+ */
+ private void sendBtSettingUpdate(boolean forceUpdate) {
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ // Adapter may be null if BT is not supported.
+ if (adapter != null) {
+ boolean btEnabled = adapter.isEnabled();
+ boolean btScanEnabled = adapter.isBleScanAlwaysAvailable();
+ if (forceUpdate || mIsBtScanningEnabled != btScanEnabled) {
+ mIsBtScanningEnabled = btScanEnabled;
+ mContextHubWrapper.onBtScanningSettingChanged(btScanEnabled);
+ }
+ if (forceUpdate || mIsBtMainEnabled != btEnabled) {
+ mIsBtMainEnabled = btEnabled;
+ mContextHubWrapper.onBtMainSettingChanged(btEnabled);
+ }
+ } else {
+ Log.d(TAG, "BT adapter not available. Defaulting to disabled");
+ if (mIsBtMainEnabled) {
+ mIsBtMainEnabled = false;
+ mContextHubWrapper.onBtMainSettingChanged(mIsBtMainEnabled);
+ }
+ if (mIsBtScanningEnabled) {
+ mIsBtScanningEnabled = false;
+ mContextHubWrapper.onBtScanningSettingChanged(mIsBtScanningEnabled);
+ }
+ }
+ }
+
+ /**
* Obtains the latest airplane mode setting value and notifies the Context Hub.
*/
private void sendAirplaneModeSettingUpdate() {
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 9b0b782..21ea1f6 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -243,6 +243,22 @@
public abstract void onMicrophoneSettingChanged(boolean enabled);
/**
+ * @return True if this version of the Contexthub HAL supports BT availability setting
+ * notifications.
+ */
+ public abstract boolean supportsBtSettingNotifications();
+
+ /**
+ * Notifies the Contexthub implementation of a BT main setting change.
+ */
+ public abstract void onBtMainSettingChanged(boolean enabled);
+
+ /**
+ * Notifies the Contexthub implementation of a BT scanning setting change.
+ */
+ public abstract void onBtScanningSettingChanged(boolean enabled);
+
+ /**
* Invoked whenever a host client connects with the framework.
*
* @param info The host endpoint info.
@@ -409,6 +425,10 @@
return true;
}
+ public boolean supportsBtSettingNotifications() {
+ return true;
+ }
+
public void onLocationSettingChanged(boolean enabled) {
onSettingChanged(android.hardware.contexthub.Setting.LOCATION, enabled);
}
@@ -432,6 +452,14 @@
onSettingChanged(android.hardware.contexthub.Setting.WIFI_SCANNING, enabled);
}
+ public void onBtMainSettingChanged(boolean enabled) {
+ onSettingChanged(android.hardware.contexthub.Setting.BT_MAIN, enabled);
+ }
+
+ public void onBtScanningSettingChanged(boolean enabled) {
+ onSettingChanged(android.hardware.contexthub.Setting.BT_SCANNING, enabled);
+ }
+
@Override
public void onHostEndpointConnected(HostEndpointInfo info) {
try {
@@ -662,8 +690,14 @@
mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
}
+ public boolean supportsBtSettingNotifications() {
+ return false;
+ }
+
public void onWifiMainSettingChanged(boolean enabled) {}
public void onWifiScanningSettingChanged(boolean enabled) {}
+ public void onBtMainSettingChanged(boolean enabled) {}
+ public void onBtScanningSettingChanged(boolean enabled) {}
}
private static class ContextHubWrapperV1_0 extends ContextHubWrapperHidl {
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 2b3f4207..05966da 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -208,7 +208,7 @@
}
@Override
- public void onError() {
+ public void onError(Throwable t) {
synchronized (mLock) {
mFlushListeners.remove(callback);
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 045a295..5047690 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -22,6 +22,7 @@
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.RecoverySystem;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.SystemProperties;
@@ -115,6 +116,13 @@
// Try one last time; if we fail again we're really in trouble
prepareUserDataLI(volumeUuid, userId, userSerial,
flags | StorageManager.FLAG_STORAGE_DE, false);
+ } else {
+ try {
+ Log.e(TAG, "prepareUserData failed", e);
+ RecoverySystem.rebootPromptAndWipeUserData(mContext, "prepareUserData failed");
+ } catch (IOException e2) {
+ throw new RuntimeException("error rebooting into recovery", e2);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d6e88f4..b07cd106 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2418,6 +2418,51 @@
}
/**
+ * Returns the remaining number of users of the given type that can be created. (taking into
+ * account the total number of users on the device as well as how many exist of that type)
+ */
+ @Override
+ public int getRemainingCreatableUserCount(String userType) {
+ checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
+ final UserTypeDetails type = mUserTypes.get(userType);
+ if (type == null || !type.isEnabled()) {
+ return 0;
+ }
+ synchronized (mUsersLock) {
+ final int userCount = getAliveUsersExcludingGuestsCountLU();
+
+ // Limit total number of users that can be created (except for guest and demo)
+ int result =
+ UserManager.isUserTypeGuest(userType) || UserManager.isUserTypeDemo(userType)
+ ? Integer.MAX_VALUE
+ : (UserManager.getMaxSupportedUsers() - userCount);
+
+ // Managed profiles have their own specific rules.
+ if (type.isManagedProfile()) {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_MANAGED_USERS)) {
+ return 0;
+ }
+ // Special case: Allow creating a managed profile anyway if there's only 1 user
+ if (result <= 0 & userCount == 1) {
+ result = 1;
+ }
+ }
+ if (result <= 0) {
+ return 0;
+ }
+
+ // Limit against max allowed for type
+ result = Math.min(result,
+ type.getMaxAllowed() == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS
+ ? Integer.MAX_VALUE
+ : (type.getMaxAllowed() - getNumberOfUsersOfType(userType)));
+
+ return Math.max(0, result);
+ }
+ }
+
+ /**
* Gets the number of users of the given user type.
* Does not include users that are about to die.
*/
@@ -2467,24 +2512,36 @@
@Override
public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
boolean allowedToRemoveOne) {
- checkQueryUsersPermission("check if more profiles can be added.");
+ return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+ }
+
+ /**
+ * Returns the remaining number of profiles of the given type that can be added to the given
+ * user. (taking into account the total number of users on the device as well as how many
+ * profiles exist of that type both in general and for the given user)
+ */
+ @Override
+ public int getRemainingCreatableProfileCount(@NonNull String userType, @UserIdInt int userId,
+ boolean allowedToRemoveOne) {
+ checkQueryOrCreateUsersPermission(
+ "get the remaining number of profiles that can be added to the given user.");
final UserTypeDetails type = mUserTypes.get(userType);
if (type == null || !type.isEnabled()) {
- return false;
+ return 0;
}
// Managed profiles have their own specific rules.
final boolean isManagedProfile = type.isManagedProfile();
if (isManagedProfile) {
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_MANAGED_USERS)) {
- return false;
+ return 0;
}
}
synchronized (mUsersLock) {
// Check if the parent exists and its type is even allowed to have a profile.
UserInfo userInfo = getUserInfoLU(userId);
if (userInfo == null || !userInfo.canHaveProfile()) {
- return false;
+ return 0;
}
final int userTypeCount = getProfileIds(userId, userType, false).length;
@@ -2493,23 +2550,30 @@
- profilesRemovedCount;
// Limit total number of users that can be created
- if (usersCountAfterRemoving >= UserManager.getMaxSupportedUsers()) {
- // Special case: Allow creating a managed profile anyway if there's only 1 user
- // Otherwise, disallow.
- if (!(isManagedProfile && usersCountAfterRemoving == 1)) {
- return false;
- }
+ int result = UserManager.getMaxSupportedUsers() - usersCountAfterRemoving;
+
+ // Special case: Allow creating a managed profile anyway if there's only 1 user
+ if (result <= 0 && isManagedProfile && usersCountAfterRemoving == 1) {
+ result = 1;
}
// Limit the number of profiles of this type that can be created.
final int maxUsersOfType = getMaxUsersOfTypePerParent(type);
if (maxUsersOfType != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
- if (userTypeCount - profilesRemovedCount >= maxUsersOfType) {
- return false;
- }
+ result = Math.min(result, maxUsersOfType - (userTypeCount - profilesRemovedCount));
}
+ if (result <= 0) {
+ return 0;
+ }
+
+ // Limit against max allowed for type (beyond max allowed per parent)
+ if (type.getMaxAllowed() != UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
+ result = Math.min(result, type.getMaxAllowed()
+ - (getNumberOfUsersOfType(userType) - profilesRemovedCount));
+ }
+
+ return Math.max(0, result);
}
- return true;
}
@GuardedBy("mUsersLock")
@@ -3791,6 +3855,7 @@
+ ". Maximum number of that type already exists.",
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
+ // Keep logic in sync with getRemainingCreatableUserCount()
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
// If the user limit has been reached, we cannot add a user (except guest/demo).
// Note that managed profiles can bypass it in certain circumstances (taken
diff --git a/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
new file mode 100644
index 0000000..f797f09
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
@@ -0,0 +1,155 @@
+/*
+ * 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.sensorprivacy;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.IoThread;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+
+class AllSensorStateController {
+
+ private static final String LOG_TAG = AllSensorStateController.class.getSimpleName();
+
+ private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
+ private static final String XML_TAG_SENSOR_PRIVACY = "all-sensor-privacy";
+ private static final String XML_TAG_SENSOR_PRIVACY_LEGACY = "sensor-privacy";
+ private static final String XML_ATTRIBUTE_ENABLED = "enabled";
+
+ private static AllSensorStateController sInstance;
+
+ private final AtomicFile mAtomicFile =
+ new AtomicFile(new File(Environment.getDataSystemDirectory(), SENSOR_PRIVACY_XML_FILE));
+
+ private boolean mEnabled;
+ private SensorPrivacyStateController.AllSensorPrivacyListener mListener;
+ private Handler mListenerHandler;
+
+ static AllSensorStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = new AllSensorStateController();
+ }
+ return sInstance;
+ }
+
+ private AllSensorStateController() {
+ if (!mAtomicFile.exists()) {
+ return;
+ }
+ try (FileInputStream inputStream = mAtomicFile.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (XML_TAG_SENSOR_PRIVACY.equals(tagName)) {
+ mEnabled |= XmlUtils
+ .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
+ break;
+ }
+ if (XML_TAG_SENSOR_PRIVACY_LEGACY.equals(tagName)) {
+ mEnabled |= XmlUtils
+ .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
+ }
+ if ("user".equals(tagName)) { // Migrate from mic/cam toggles format
+ int user = XmlUtils.readIntAttribute(parser, "id", -1);
+ if (user == 0) {
+ mEnabled |=
+ XmlUtils.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED);
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ mEnabled = false;
+ }
+ }
+
+ public boolean getAllSensorStateLocked() {
+ return mEnabled;
+ }
+
+ public void setAllSensorStateLocked(boolean enabled) {
+ if (mEnabled != enabled) {
+ mEnabled = enabled;
+ if (mListener != null && mListenerHandler != null) {
+ mListenerHandler.sendMessage(
+ PooledLambda.obtainMessage(mListener::onAllSensorPrivacyChanged, enabled));
+ }
+ }
+ }
+
+ void setAllSensorPrivacyListenerLocked(Handler handler,
+ SensorPrivacyStateController.AllSensorPrivacyListener listener) {
+ Objects.requireNonNull(handler);
+ Objects.requireNonNull(listener);
+ if (mListener != null) {
+ throw new IllegalStateException("Listener is already set");
+ }
+ mListener = listener;
+ mListenerHandler = handler;
+ }
+
+ public void schedulePersistLocked() {
+ IoThread.getHandler().sendMessage(PooledLambda.obtainMessage(this::persist, mEnabled));
+ }
+
+ private void persist(boolean enabled) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mAtomicFile.startWrite();
+ TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+
+ void resetForTesting() {
+ mListener = null;
+ mListenerHandler = null;
+ mEnabled = false;
+ }
+
+ void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
+ // TODO stub
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/OWNERS b/services/core/java/com/android/server/sensorprivacy/OWNERS
new file mode 100644
index 0000000..15e3f7a
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/native:/libs/sensorprivacy/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/sensorprivacy/PersistedState.java b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
new file mode 100644
index 0000000..ce9fff5
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
@@ -0,0 +1,494 @@
+/*
+ * 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.sensorprivacy;
+
+import android.hardware.SensorPrivacyManager;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.service.SensorPrivacyIndividualEnabledSensorProto;
+import android.service.SensorPrivacySensorProto;
+import android.service.SensorPrivacyServiceDumpProto;
+import android.service.SensorPrivacyUserProto;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.QuadConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Class for managing persisted state. Synchronization must be handled by the caller.
+ */
+class PersistedState {
+
+ private static final String LOG_TAG = PersistedState.class.getSimpleName();
+
+ /** Version number indicating compatibility parsing the persisted file */
+ private static final int CURRENT_PERSISTENCE_VERSION = 2;
+ /** Version number indicating the persisted data needs upgraded to match new internal data
+ * structures and features */
+ private static final int CURRENT_VERSION = 2;
+
+ private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
+ private static final String XML_TAG_SENSOR_STATE = "sensor-state";
+ private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
+ private static final String XML_ATTRIBUTE_VERSION = "version";
+ private static final String XML_ATTRIBUTE_TOGGLE_TYPE = "toggle-type";
+ private static final String XML_ATTRIBUTE_USER_ID = "user-id";
+ private static final String XML_ATTRIBUTE_SENSOR = "sensor";
+ private static final String XML_ATTRIBUTE_STATE_TYPE = "state-type";
+ private static final String XML_ATTRIBUTE_LAST_CHANGE = "last-change";
+
+ private final AtomicFile mAtomicFile;
+
+ private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
+
+ static PersistedState fromFile(String fileName) {
+ return new PersistedState(fileName);
+ }
+
+ private PersistedState(String fileName) {
+ mAtomicFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), fileName));
+ readState();
+ }
+
+ private void readState() {
+ AtomicFile file = mAtomicFile;
+ if (!file.exists()) {
+ AtomicFile fileToMigrateFrom =
+ new AtomicFile(new File(Environment.getDataSystemDirectory(),
+ "sensor_privacy.xml"));
+
+ if (fileToMigrateFrom.exists()) {
+ // Sample the start tag to determine if migration is needed
+ try (FileInputStream inputStream = fileToMigrateFrom.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+ file = fileToMigrateFrom;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ // Delete the file to prevent the same error on subsequent calls and assume
+ // sensor privacy is not enabled.
+ fileToMigrateFrom.delete();
+ } catch (XmlPullParserException e) {
+ // No migration needed
+ }
+ }
+ }
+
+ Object nonupgradedState = null;
+ if (file.exists()) {
+ try (FileInputStream inputStream = file.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+ final int persistenceVersion = parser.getAttributeInt(null,
+ XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
+
+ // Use inline string literals for xml tags/attrs when parsing old versions since
+ // these should never be changed even with refactorings.
+ if (persistenceVersion == 0) {
+ int version = 0;
+ PVersion0 version0 = new PVersion0(version);
+ nonupgradedState = version0;
+ readPVersion0(parser, version0);
+ } else if (persistenceVersion == 1) {
+ int version = parser.getAttributeInt(null,
+ "version", 1);
+ PVersion1 version1 = new PVersion1(version);
+ nonupgradedState = version1;
+
+ readPVersion1(parser, version1);
+ } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
+ int version = parser.getAttributeInt(null,
+ XML_ATTRIBUTE_VERSION, 2);
+ PVersion2 version2 = new PVersion2(version);
+ nonupgradedState = version2;
+
+ readPVersion2(parser, version2);
+ } else {
+ Log.e(LOG_TAG, "Unknown persistence version: " + persistenceVersion
+ + ". Deleting.",
+ new RuntimeException());
+ file.delete();
+ nonupgradedState = null;
+ }
+
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ // Delete the file to prevent the same error on subsequent calls and assume
+ // sensor privacy is not enabled.
+ file.delete();
+ nonupgradedState = null;
+ }
+ }
+
+ if (nonupgradedState == null) {
+ // New file, default state for current version goes here.
+ nonupgradedState = new PVersion2(2);
+ }
+
+ if (nonupgradedState instanceof PVersion0) {
+ nonupgradedState = PVersion1.fromPVersion0((PVersion0) nonupgradedState);
+ }
+ if (nonupgradedState instanceof PVersion1) {
+ nonupgradedState = PVersion2.fromPVersion1((PVersion1) nonupgradedState);
+ }
+ if (nonupgradedState instanceof PVersion2) {
+ PVersion2 upgradedState = (PVersion2) nonupgradedState;
+ mStates = upgradedState.mStates;
+ } else {
+ Log.e(LOG_TAG, "State not successfully upgraded.");
+ mStates = new ArrayMap<>();
+ }
+ }
+
+ private static void readPVersion0(TypedXmlPullParser parser, PVersion0 version0)
+ throws XmlPullParserException, IOException {
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("individual-sensor-privacy".equals(parser.getName())) {
+ int sensor = XmlUtils.readIntAttribute(parser, "sensor");
+ boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
+ "enabled");
+ version0.addState(sensor, indEnabled);
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ }
+
+ private static void readPVersion1(TypedXmlPullParser parser, PVersion1 version1)
+ throws XmlPullParserException, IOException {
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ XmlUtils.nextElement(parser);
+
+ if ("user".equals(parser.getName())) {
+ int currentUserId = parser.getAttributeInt(null, "id");
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if ("individual-sensor-privacy".equals(parser.getName())) {
+ int sensor = parser.getAttributeInt(null, "sensor");
+ boolean isEnabled = parser.getAttributeBoolean(null,
+ "enabled");
+ version1.addState(currentUserId, sensor, isEnabled);
+ }
+ }
+ }
+ }
+ }
+
+ private static void readPVersion2(TypedXmlPullParser parser, PVersion2 version2)
+ throws XmlPullParserException, IOException {
+
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ XmlUtils.nextElement(parser);
+
+ if (XML_TAG_SENSOR_STATE.equals(parser.getName())) {
+ int toggleType = parser.getAttributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE);
+ int userId = parser.getAttributeInt(null, XML_ATTRIBUTE_USER_ID);
+ int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
+ int state = parser.getAttributeInt(null, XML_ATTRIBUTE_STATE_TYPE);
+ long lastChange = parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE);
+
+ version2.addState(toggleType, userId, sensor, state, lastChange);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ public SensorState getState(int toggleType, int userId, int sensor) {
+ return mStates.get(new TypeUserSensor(toggleType, userId, sensor));
+ }
+
+ public SensorState setState(int toggleType, int userId, int sensor, SensorState sensorState) {
+ return mStates.put(new TypeUserSensor(toggleType, userId, sensor), sensorState);
+ }
+
+ private static class TypeUserSensor {
+
+ int mType;
+ int mUserId;
+ int mSensor;
+
+ TypeUserSensor(int type, int userId, int sensor) {
+ mType = type;
+ mUserId = userId;
+ mSensor = sensor;
+ }
+
+ TypeUserSensor(TypeUserSensor typeUserSensor) {
+ this(typeUserSensor.mType, typeUserSensor.mUserId, typeUserSensor.mSensor);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TypeUserSensor)) return false;
+ TypeUserSensor that = (TypeUserSensor) o;
+ return mType == that.mType && mUserId == that.mUserId && mSensor == that.mSensor;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * (31 * mType + mUserId) + mSensor;
+ }
+ }
+
+ void schedulePersist() {
+ int numStates = mStates.size();
+
+ ArrayMap<TypeUserSensor, SensorState> statesCopy = new ArrayMap<>();
+ for (int i = 0; i < numStates; i++) {
+ statesCopy.put(new TypeUserSensor(mStates.keyAt(i)),
+ new SensorState(mStates.valueAt(i)));
+ }
+ IoThread.getHandler().sendMessage(
+ PooledLambda.obtainMessage(PersistedState::persist, this, statesCopy));
+ }
+
+ private void persist(ArrayMap<TypeUserSensor, SensorState> states) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mAtomicFile.startWrite();
+ TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attributeInt(null, XML_ATTRIBUTE_PERSISTENCE_VERSION,
+ CURRENT_PERSISTENCE_VERSION);
+ serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
+ for (int i = 0; i < states.size(); i++) {
+ TypeUserSensor userSensor = states.keyAt(i);
+ SensorState sensorState = states.valueAt(i);
+
+ serializer.startTag(null, XML_TAG_SENSOR_STATE);
+ serializer.attributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE,
+ userSensor.mType);
+ serializer.attributeInt(null, XML_ATTRIBUTE_USER_ID,
+ userSensor.mUserId);
+ serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR,
+ userSensor.mSensor);
+ serializer.attributeInt(null, XML_ATTRIBUTE_STATE_TYPE,
+ sensorState.getState());
+ serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE,
+ sensorState.getLastChange());
+ serializer.endTag(null, XML_TAG_SENSOR_STATE);
+ }
+
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+
+ void dump(DualDumpOutputStream dumpStream) {
+ // Collect per user, then per sensor. <toggle type, state>
+ SparseArray<SparseArray<Pair<Integer, SensorState>>> statesMatrix = new SparseArray<>();
+ int numStates = mStates.size();
+ for (int i = 0; i < numStates; i++) {
+ int toggleType = mStates.keyAt(i).mType;
+ int userId = mStates.keyAt(i).mUserId;
+ int sensor = mStates.keyAt(i).mSensor;
+
+ SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.get(userId);
+ if (userStates == null) {
+ userStates = new SparseArray<>();
+ statesMatrix.put(userId, userStates);
+ }
+ userStates.put(sensor, new Pair<>(toggleType, mStates.valueAt(i)));
+ }
+
+ dumpStream.write("storage_implementation",
+ SensorPrivacyServiceDumpProto.STORAGE_IMPLEMENTATION,
+ SensorPrivacyStateControllerImpl.class.getName());
+
+ int numUsers = statesMatrix.size();
+ for (int i = 0; i < numUsers; i++) {
+ int userId = statesMatrix.keyAt(i);
+ long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
+ dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
+ SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.valueAt(i);
+ int numSensors = userStates.size();
+ for (int j = 0; j < numSensors; j++) {
+ int sensor = userStates.keyAt(j);
+ int toggleType = userStates.valueAt(j).first;
+ SensorState sensorState = userStates.valueAt(j).second;
+ long sensorToken = dumpStream.start("sensors", SensorPrivacyUserProto.SENSORS);
+ dumpStream.write("sensor", SensorPrivacySensorProto.SENSOR, sensor);
+ long toggleToken = dumpStream.start("toggles", SensorPrivacySensorProto.TOGGLES);
+ dumpStream.write("toggle_type",
+ SensorPrivacyIndividualEnabledSensorProto.TOGGLE_TYPE,
+ toggleType);
+ dumpStream.write("state_type",
+ SensorPrivacyIndividualEnabledSensorProto.STATE_TYPE,
+ sensorState.getState());
+ dumpStream.write("last_change",
+ SensorPrivacyIndividualEnabledSensorProto.LAST_CHANGE,
+ sensorState.getLastChange());
+ dumpStream.end(toggleToken);
+ dumpStream.end(sensorToken);
+ }
+ dumpStream.end(userToken);
+ }
+ }
+
+ void forEachKnownState(QuadConsumer<Integer, Integer, Integer, SensorState> consumer) {
+ int numStates = mStates.size();
+ for (int i = 0; i < numStates; i++) {
+ TypeUserSensor tus = mStates.keyAt(i);
+ SensorState sensorState = mStates.valueAt(i);
+ consumer.accept(tus.mType, tus.mUserId, tus.mSensor, sensorState);
+ }
+ }
+
+ // Structure for persistence version 0
+ private static class PVersion0 {
+ private SparseArray<SensorState> mIndividualEnabled = new SparseArray<>();
+
+ private PVersion0(int version) {
+ if (version != 0) {
+ throw new RuntimeException("Only version 0 supported");
+ }
+ }
+
+ private void addState(int sensor, boolean enabled) {
+ mIndividualEnabled.put(sensor, new SensorState(enabled));
+ }
+
+ private void upgrade() {
+ // No op, only version 0 is supported
+ }
+ }
+
+ // Structure for persistence version 1
+ private static class PVersion1 {
+ private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
+
+ private PVersion1(int version) {
+ if (version != 1) {
+ throw new RuntimeException("Only version 1 supported");
+ }
+ }
+
+ private static PVersion1 fromPVersion0(PVersion0 version0) {
+ version0.upgrade();
+
+ PVersion1 result = new PVersion1(1);
+
+ int[] users = {UserHandle.USER_SYSTEM};
+ try {
+ users = LocalServices.getService(UserManagerInternal.class).getUserIds();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Unable to get users.", e);
+ }
+
+ // Copy global state to each user
+ for (int i = 0; i < users.length; i++) {
+ int userId = users[i];
+
+ for (int j = 0; j < version0.mIndividualEnabled.size(); j++) {
+ final int sensor = version0.mIndividualEnabled.keyAt(j);
+ final SensorState sensorState = version0.mIndividualEnabled.valueAt(j);
+
+ result.addState(userId, sensor, sensorState.isEnabled());
+ }
+ }
+
+ return result;
+ }
+
+ private void addState(int userId, int sensor, boolean enabled) {
+ SparseArray<SensorState> userIndividualSensorEnabled =
+ mIndividualEnabled.get(userId, new SparseArray<>());
+ mIndividualEnabled.put(userId, userIndividualSensorEnabled);
+
+ userIndividualSensorEnabled
+ .put(sensor, new SensorState(enabled));
+ }
+
+ private void upgrade() {
+ // No op, only version 1 is supported
+ }
+ }
+
+ // Structure for persistence version 2
+ private static class PVersion2 {
+ private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
+
+ private PVersion2(int version) {
+ if (version != 2) {
+ throw new RuntimeException("Only version 2 supported");
+ }
+ }
+
+ private static PVersion2 fromPVersion1(PVersion1 version1) {
+ version1.upgrade();
+
+ PVersion2 result = new PVersion2(2);
+
+ SparseArray<SparseArray<SensorState>> individualEnabled =
+ version1.mIndividualEnabled;
+ int numUsers = individualEnabled.size();
+ for (int i = 0; i < numUsers; i++) {
+ int userId = individualEnabled.keyAt(i);
+ SparseArray<SensorState> userIndividualEnabled = individualEnabled.valueAt(i);
+ int numSensors = userIndividualEnabled.size();
+ for (int j = 0; j < numSensors; j++) {
+ int sensor = userIndividualEnabled.keyAt(j);
+ SensorState sensorState = userIndividualEnabled.valueAt(j);
+ result.addState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ userId, sensor, sensorState.getState(), sensorState.getLastChange());
+ }
+ }
+
+ return result;
+ }
+
+ private void addState(int toggleType, int userId, int sensor, int state,
+ long lastChange) {
+ mStates.put(new TypeUserSensor(toggleType, userId, sensor),
+ new SensorState(state, lastChange));
+ }
+ }
+
+ public void resetForTesting() {
+ mStates = new ArrayMap<>();
+ }
+}
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
similarity index 71%
rename from services/core/java/com/android/server/SensorPrivacyService.java
rename to services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 7a4d11c..e9b5f11 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.sensorprivacy;
import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
@@ -40,10 +40,10 @@
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
import static android.os.UserHandle.USER_NULL;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
@@ -83,7 +83,6 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -97,26 +96,19 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.SensorPrivacyIndividualEnabledSensorProto;
-import android.service.SensorPrivacyServiceDumpProto;
-import android.service.SensorPrivacyUserProto;
import android.service.voice.VoiceInteractionManagerInternal;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.text.Html;
+import android.text.Spanned;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
-import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -125,19 +117,14 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FunctionalUtils;
-import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -151,32 +138,10 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_LOGGING = false;
- /** Version number indicating compatibility parsing the persisted file */
- private static final int CURRENT_PERSISTENCE_VERSION = 1;
- /** Version number indicating the persisted data needs upgraded to match new internal data
- * structures and features */
- private static final int CURRENT_VERSION = 1;
-
- private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
- private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
- private static final String XML_TAG_USER = "user";
- private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy";
- private static final String XML_ATTRIBUTE_ID = "id";
- private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
- private static final String XML_ATTRIBUTE_VERSION = "version";
- private static final String XML_ATTRIBUTE_ENABLED = "enabled";
- private static final String XML_ATTRIBUTE_LAST_CHANGE = "last-change";
- private static final String XML_ATTRIBUTE_SENSOR = "sensor";
-
private static final String SENSOR_PRIVACY_CHANNEL_ID = Context.SENSOR_PRIVACY_SERVICE;
private static final String ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY =
SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
- // These are associated with fields that existed for older persisted versions of files
- private static final int VER0_ENABLED = 0;
- private static final int VER0_INDIVIDUAL_ENABLED = 1;
- private static final int VER1_ENABLED = 0;
- private static final int VER1_INDIVIDUAL_ENABLED = 1;
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
private final Context mContext;
@@ -200,6 +165,7 @@
public SensorPrivacyService(Context context) {
super(context);
+
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManagerInternal = getLocalService(AppOpsManagerInternal.class);
@@ -247,12 +213,8 @@
private final SensorPrivacyHandler mHandler;
private final Object mLock = new Object();
- @GuardedBy("mLock")
- private final AtomicFile mAtomicFile;
- @GuardedBy("mLock")
- private SparseBooleanArray mEnabled = new SparseBooleanArray();
- @GuardedBy("mLock")
- private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
+
+ private SensorPrivacyStateController mSensorPrivacyStateController;
/**
* Packages for which not to show sensor use reminders.
@@ -266,34 +228,6 @@
private final ArrayMap<SensorUseReminderDialogInfo, ArraySet<Integer>>
mQueuedSensorUseReminderDialogs = new ArrayMap<>();
- private class SensorState {
- private boolean mEnabled;
- private long mLastChange;
-
- SensorState(boolean enabled) {
- mEnabled = enabled;
- mLastChange = getCurrentTimeMillis();
- }
-
- SensorState(boolean enabled, long lastChange) {
- mEnabled = enabled;
- if (lastChange < 0) {
- mLastChange = getCurrentTimeMillis();
- } else {
- mLastChange = lastChange;
- }
- }
-
- boolean setEnabled(boolean enabled) {
- if (mEnabled != enabled) {
- mEnabled = enabled;
- mLastChange = getCurrentTimeMillis();
- return true;
- }
- return false;
- }
- }
-
private class SensorUseReminderDialogInfo {
private int mTaskId;
private UserHandle mUser;
@@ -323,14 +257,7 @@
SensorPrivacyServiceImpl() {
mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
- File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
- SENSOR_PRIVACY_XML_FILE);
- mAtomicFile = new AtomicFile(sensorPrivacyFile);
- synchronized (mLock) {
- if (readPersistedSensorPrivacyStateLocked()) {
- persistSensorPrivacyStateLocked();
- }
- }
+ mSensorPrivacyStateController = SensorPrivacyStateController.getInstance();
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
OP_CAMERA, OP_PHONE_CALL_CAMERA};
@@ -349,7 +276,26 @@
}
}, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED);
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSensorPrivacyStateController.forEachState(
+ (toggleType, userId, sensor, state) ->
+ logSensorPrivacyToggle(OTHER, sensor, state.isEnabled(),
+ state.getLastChange(), true)
+ );
+ }
+ }, new IntentFilter(Intent.ACTION_SHUTDOWN));
+
mUserManagerInternal.addUserRestrictionsListener(this);
+
+ mSensorPrivacyStateController.setAllSensorPrivacyListener(
+ mHandler, mHandler::handleSensorPrivacyChanged);
+ mSensorPrivacyStateController.setSensorPrivacyListener(
+ mHandler,
+ (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged(
+ userId, sensor, state.isEnabled()));
}
@Override
@@ -490,8 +436,9 @@
}
}
- String inputMethodComponent = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
+ String inputMethodComponent = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
+ mCurrentUser);
String inputMethodPackageName = null;
if (inputMethodComponent != null) {
inputMethodPackageName = ComponentName.unflattenFromString(
@@ -546,7 +493,8 @@
private void enqueueSensorUseReminderDialogAsync(int taskId, @NonNull UserHandle user,
@NonNull String packageName, int sensor) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- this:: enqueueSensorUseReminderDialog, taskId, user, packageName, sensor));
+ SensorPrivacyServiceImpl::enqueueSensorUseReminderDialog, this, taskId, user,
+ packageName, sensor));
}
private void enqueueSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
@@ -564,8 +512,8 @@
sensors.add(sensor);
}
mQueuedSensorUseReminderDialogs.put(info, sensors);
- mHandler.sendMessageDelayed(
- PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info),
+ mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::showSensorUserReminderDialog, this, info),
REMINDER_DIALOG_DELAY_MILLIS);
return;
}
@@ -655,28 +603,32 @@
notificationManager.createNotificationChannel(channel);
Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes);
+
+ String contentTitle = getUiContext().getString(messageRes);
+ Spanned contentText = Html.fromHtml(getUiContext().getString(
+ R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
+ PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
+ new Intent(Settings.ACTION_PRIVACY_SETTINGS),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ String actionTitle = getUiContext().getString(
+ R.string.sensor_privacy_start_use_dialog_turn_on_button);
+ PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, sensor,
+ new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
+ .setPackage(mContext.getPackageName())
+ .putExtra(EXTRA_SENSOR, sensor)
+ .putExtra(Intent.EXTRA_USER, user),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT);
notificationManager.notify(notificationId,
new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID)
- .setContentTitle(getUiContext().getString(messageRes))
- .setContentText(Html.fromHtml(getUiContext().getString(
- R.string.sensor_privacy_start_use_notification_content_text,
- packageLabel),0))
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
.setSmallIcon(icon)
.addAction(new Notification.Action.Builder(icon,
- getUiContext().getString(
- R.string.sensor_privacy_start_use_dialog_turn_on_button),
- PendingIntent.getBroadcast(mContext, sensor,
- new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
- .setPackage(mContext.getPackageName())
- .putExtra(EXTRA_SENSOR, sensor)
- .putExtra(Intent.EXTRA_USER, user),
- PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT))
- .build())
- .setContentIntent(PendingIntent.getActivity(mContext, sensor,
- new Intent(Settings.ACTION_PRIVACY_SETTINGS),
- PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT))
+ actionTitle, actionIntent).build())
+ .setContentIntent(contentIntent)
.extend(new Notification.TvExtender())
.setTimeoutAfter(isTelevision(mContext)
? /* dismiss immediately */ 1
@@ -697,16 +649,7 @@
@Override
public void setSensorPrivacy(boolean enable) {
enforceManageSensorPrivacyPermission();
- // Keep the state consistent between all users to make it a single global state
- forAllUsers(userId -> setSensorPrivacy(userId, enable));
- }
-
- private void setSensorPrivacy(@UserIdInt int userId, boolean enable) {
- synchronized (mLock) {
- mEnabled.put(userId, enable);
- persistSensorPrivacyStateLocked();
- }
- mHandler.onSensorPrivacyChanged(enable);
+ mSensorPrivacyStateController.setAllSensorState(enable);
}
@Override
@@ -735,43 +678,23 @@
private void setIndividualSensorPrivacyUnchecked(int userId, int source, int sensor,
boolean enable) {
- synchronized (mLock) {
- SparseArray<SensorState> userIndividualEnabled = mIndividualEnabled.get(userId,
- new SparseArray<>());
- SensorState sensorState = userIndividualEnabled.get(sensor);
- long lastChange;
- if (sensorState != null) {
- lastChange = sensorState.mLastChange;
- if (!sensorState.setEnabled(enable)) {
- // State not changing
- return;
- }
- } else {
- sensorState = new SensorState(enable);
- lastChange = sensorState.mLastChange;
- userIndividualEnabled.put(sensor, sensorState);
- }
- mIndividualEnabled.put(userId, userIndividualEnabled);
-
- if (userId == mUserManagerInternal.getProfileParentId(userId)) {
- logSensorPrivacyToggle(source, sensor, sensorState.mEnabled, lastChange);
- }
-
- if (!enable) {
- final long token = Binder.clearCallingIdentity();
- try {
- // Remove any notifications prompting the user to disable sensory privacy
- NotificationManager notificationManager =
- mContext.getSystemService(NotificationManager.class);
-
- notificationManager.cancel(sensor);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- persistSensorPrivacyState();
- }
- mHandler.onSensorPrivacyChanged(userId, sensor, enable);
+ final long[] lastChange = new long[1];
+ mSensorPrivacyStateController.atomic(() -> {
+ SensorState sensorState = mSensorPrivacyStateController
+ .getState(SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor);
+ lastChange[0] = sensorState.getLastChange();
+ mSensorPrivacyStateController.setState(
+ SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor, enable, mHandler,
+ changeSuccessful -> {
+ if (changeSuccessful) {
+ if (userId == mUserManagerInternal.getProfileParentId(userId)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::logSensorPrivacyToggle, this,
+ source, sensor, enable, lastChange[0], false));
+ }
+ }
+ });
+ });
}
private boolean canChangeIndividualSensorPrivacy(@UserIdInt int userId, int sensor) {
@@ -801,14 +724,23 @@
}
private void logSensorPrivacyToggle(int source, int sensor, boolean enabled,
- long lastChange) {
+ long lastChange, boolean onShutDown) {
long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60));
int logAction = -1;
- if (enabled) {
- logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+ if (onShutDown) {
+ // TODO ACTION_POWER_OFF_WHILE_(ON/OFF)
+ if (enabled) {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+ } else {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+ }
} else {
- logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+ if (enabled) {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+ } else {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+ }
}
int logSensor = -1;
@@ -895,13 +827,7 @@
@Override
public boolean isSensorPrivacyEnabled() {
enforceObserveSensorPrivacyPermission();
- return isSensorPrivacyEnabled(USER_SYSTEM);
- }
-
- private boolean isSensorPrivacyEnabled(@UserIdInt int userId) {
- synchronized (mLock) {
- return mEnabled.get(userId, false);
- }
+ return mSensorPrivacyStateController.getAllSensorState();
}
@Override
@@ -918,239 +844,8 @@
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
}
- synchronized (mLock) {
- return isIndividualSensorPrivacyEnabledLocked(userId, sensor);
- }
- }
-
- private boolean isIndividualSensorPrivacyEnabledLocked(int userId, int sensor) {
- SparseArray<SensorState> states = mIndividualEnabled.get(userId);
- if (states == null) {
- return false;
- }
- SensorState state = states.get(sensor);
- if (state == null) {
- return false;
- }
- return state.mEnabled;
- }
-
- /**
- * Returns the state of sensor privacy from persistent storage.
- */
- private boolean readPersistedSensorPrivacyStateLocked() {
- // if the file does not exist then sensor privacy has not yet been enabled on
- // the device.
-
- SparseArray<Object> map = new SparseArray<>();
- int version = -1;
-
- if (mAtomicFile.exists()) {
- try (FileInputStream inputStream = mAtomicFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
- XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
- final int persistenceVersion = parser.getAttributeInt(null,
- XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
-
- // Use inline string literals for xml tags/attrs when parsing old versions since
- // these should never be changed even with refactorings.
- if (persistenceVersion == 0) {
- boolean enabled = parser.getAttributeBoolean(null, "enabled", false);
- SparseArray<SensorState> individualEnabled = new SparseArray<>();
- version = 0;
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if ("individual-sensor-privacy".equals(tagName)) {
- int sensor = XmlUtils.readIntAttribute(parser, "sensor");
- boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
- "enabled");
- individualEnabled.put(sensor, new SensorState(indEnabled));
- XmlUtils.skipCurrentTag(parser);
- } else {
- XmlUtils.nextElement(parser);
- }
- }
- map.put(VER0_ENABLED, enabled);
- map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled);
- } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
- SparseBooleanArray enabled = new SparseBooleanArray();
- SparseArray<SparseArray<SensorState>> individualEnabled =
- new SparseArray<>();
- version = parser.getAttributeInt(null,
- XML_ATTRIBUTE_VERSION, 1);
-
- int currentUserId = -1;
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- XmlUtils.nextElement(parser);
- String tagName = parser.getName();
- if (XML_TAG_USER.equals(tagName)) {
- currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID);
- boolean isEnabled = parser.getAttributeBoolean(null,
- XML_ATTRIBUTE_ENABLED);
- if (enabled.indexOfKey(currentUserId) >= 0) {
- Log.e(TAG, "User listed multiple times in file.",
- new RuntimeException());
- mAtomicFile.delete();
- version = -1;
- break;
- }
-
- if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
- // User may no longer exist, skip this user
- currentUserId = -1;
- continue;
- }
-
- enabled.put(currentUserId, isEnabled);
- }
- if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) {
- if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
- // User may no longer exist or isn't set
- continue;
- }
- int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
- boolean isEnabled = parser.getAttributeBoolean(null,
- XML_ATTRIBUTE_ENABLED);
- long lastChange = parser
- .getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, -1);
- SparseArray<SensorState> userIndividualEnabled =
- individualEnabled.get(currentUserId, new SparseArray<>());
-
- userIndividualEnabled
- .put(sensor, new SensorState(isEnabled, lastChange));
- individualEnabled.put(currentUserId, userIndividualEnabled);
- }
- }
-
- map.put(VER1_ENABLED, enabled);
- map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled);
- } else {
- Log.e(TAG, "Unknown persistence version: " + persistenceVersion
- + ". Deleting.",
- new RuntimeException());
- mAtomicFile.delete();
- version = -1;
- }
-
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Caught an exception reading the state from storage: ", e);
- // Delete the file to prevent the same error on subsequent calls and assume
- // sensor privacy is not enabled.
- mAtomicFile.delete();
- version = -1;
- }
- }
-
- try {
- return upgradeAndInit(version, map);
- } catch (Exception e) {
- Log.wtf(TAG, "Failed to upgrade and set sensor privacy state,"
- + " resetting to default.", e);
- mEnabled = new SparseBooleanArray();
- mIndividualEnabled = new SparseArray<>();
- return true;
- }
- }
-
- private boolean upgradeAndInit(int version, SparseArray map) {
- if (version == -1) {
- // New file, default state for current version goes here.
- mEnabled = new SparseBooleanArray();
- mIndividualEnabled = new SparseArray<>();
- forAllUsers(userId -> mEnabled.put(userId, false));
- forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseArray<>()));
- return true;
- }
- boolean upgraded = false;
- final int[] users = getLocalService(UserManagerInternal.class).getUserIds();
- if (version == 0) {
- final boolean enabled = (boolean) map.get(VER0_ENABLED);
- final SparseArray<SensorState> individualEnabled =
- (SparseArray<SensorState>) map.get(VER0_INDIVIDUAL_ENABLED);
-
- final SparseBooleanArray perUserEnabled = new SparseBooleanArray();
- final SparseArray<SparseArray<SensorState>> perUserIndividualEnabled =
- new SparseArray<>();
-
- // Copy global state to each user
- for (int i = 0; i < users.length; i++) {
- int user = users[i];
- perUserEnabled.put(user, enabled);
- SparseArray<SensorState> userIndividualSensorEnabled = new SparseArray<>();
- perUserIndividualEnabled.put(user, userIndividualSensorEnabled);
- for (int j = 0; j < individualEnabled.size(); j++) {
- final int sensor = individualEnabled.keyAt(j);
- final SensorState isSensorEnabled = individualEnabled.valueAt(j);
- userIndividualSensorEnabled.put(sensor, isSensorEnabled);
- }
- }
-
- map.clear();
- map.put(VER1_ENABLED, perUserEnabled);
- map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled);
-
- version = 1;
- upgraded = true;
- }
- if (version == CURRENT_VERSION) {
- mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED);
- mIndividualEnabled =
- (SparseArray<SparseArray<SensorState>>) map.get(VER1_INDIVIDUAL_ENABLED);
- }
- return upgraded;
- }
-
- /**
- * Persists the state of sensor privacy.
- */
- private void persistSensorPrivacyState() {
- synchronized (mLock) {
- persistSensorPrivacyStateLocked();
- }
- }
-
- private void persistSensorPrivacyStateLocked() {
- FileOutputStream outputStream = null;
- try {
- outputStream = mAtomicFile.startWrite();
- TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
- serializer.startDocument(null, true);
- serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
- serializer.attributeInt(
- null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
- serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
- forAllUsers(userId -> {
- serializer.startTag(null, XML_TAG_USER);
- serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId);
- serializer.attributeBoolean(
- null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId));
-
- SparseArray<SensorState> individualEnabled =
- mIndividualEnabled.get(userId, new SparseArray<>());
- int numIndividual = individualEnabled.size();
- for (int i = 0; i < numIndividual; i++) {
- serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
- int sensor = individualEnabled.keyAt(i);
- SensorState sensorState = individualEnabled.valueAt(i);
- boolean enabled = sensorState.mEnabled;
- long lastChange = sensorState.mLastChange;
- serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
- serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
- serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, lastChange);
- serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
- }
- serializer.endTag(null, XML_TAG_USER);
-
- });
- serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
- serializer.endDocument();
- mAtomicFile.finishWrite(outputStream);
- } catch (IOException e) {
- Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
- mAtomicFile.failWrite(outputStream);
- }
+ return mSensorPrivacyStateController.getState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ userId, sensor).isEnabled();
}
@Override
@@ -1285,23 +980,23 @@
}
private void userSwitching(int from, int to) {
- boolean micState;
- boolean camState;
- boolean prevMicState;
- boolean prevCamState;
- synchronized (mLock) {
- prevMicState = isIndividualSensorPrivacyEnabledLocked(from, MICROPHONE);
- prevCamState = isIndividualSensorPrivacyEnabledLocked(from, CAMERA);
- micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
- camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
+ final boolean[] micState = new boolean[1];
+ final boolean[] camState = new boolean[1];
+ final boolean[] prevMicState = new boolean[1];
+ final boolean[] prevCamState = new boolean[1];
+ mSensorPrivacyStateController.atomic(() -> {
+ prevMicState[0] = isIndividualSensorPrivacyEnabled(from, MICROPHONE);
+ prevCamState[0] = isIndividualSensorPrivacyEnabled(from, CAMERA);
+ micState[0] = isIndividualSensorPrivacyEnabled(to, MICROPHONE);
+ camState[0] = isIndividualSensorPrivacyEnabled(to, CAMERA);
+ });
+ if (from == USER_NULL || prevMicState[0] != micState[0]) {
+ mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState[0]);
+ setGlobalRestriction(MICROPHONE, micState[0]);
}
- if (from == USER_NULL || prevMicState != micState) {
- mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
- setGlobalRestriction(MICROPHONE, micState);
- }
- if (from == USER_NULL || prevCamState != camState) {
- mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
- setGlobalRestriction(CAMERA, camState);
+ if (from == USER_NULL || prevCamState[0] != camState[0]) {
+ mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState[0]);
+ setGlobalRestriction(CAMERA, camState[0]);
}
}
@@ -1395,12 +1090,14 @@
final long identity = Binder.clearCallingIdentity();
try {
if (dumpAsProto) {
- dump(new DualDumpOutputStream(new ProtoOutputStream(fd)));
+ mSensorPrivacyStateController.dump(
+ new DualDumpOutputStream(new ProtoOutputStream(fd)));
} else {
pw.println("SENSOR PRIVACY MANAGER STATE (dumpsys "
+ Context.SENSOR_PRIVACY_SERVICE + ")");
- dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
+ mSensorPrivacyStateController.dump(
+ new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1408,45 +1105,6 @@
}
/**
- * Dump state to {@link DualDumpOutputStream}.
- *
- * @param dumpStream The destination to dump to
- */
- private void dump(@NonNull DualDumpOutputStream dumpStream) {
- synchronized (mLock) {
-
- forAllUsers(userId -> {
- long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
- dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
- dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED,
- mEnabled.get(userId, false));
-
- SparseArray<SensorState> individualEnabled = mIndividualEnabled.get(userId);
- if (individualEnabled != null) {
- int numIndividualEnabled = individualEnabled.size();
- for (int i = 0; i < numIndividualEnabled; i++) {
- long individualToken = dumpStream.start("individual_enabled_sensor",
- SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR);
-
- dumpStream.write("sensor",
- SensorPrivacyIndividualEnabledSensorProto.SENSOR,
- individualEnabled.keyAt(i));
- dumpStream.write("is_enabled",
- SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
- individualEnabled.valueAt(i).mEnabled);
- // TODO dump last change
-
- dumpStream.end(individualToken);
- }
- }
- dumpStream.end(userToken);
- });
- }
-
- dumpStream.flush();
- }
-
- /**
* Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}.
*
* @param sensor The name to convert
@@ -1504,25 +1162,6 @@
setIndividualSensorPrivacy(userId, SHELL, sensor, false);
}
break;
- case "reset": {
- int sensor = sensorStrToId(getNextArgRequired());
- if (sensor == UNKNOWN) {
- pw.println("Invalid sensor");
- return -1;
- }
-
- enforceManageSensorPrivacyPermission();
-
- synchronized (mLock) {
- SparseArray<SensorState> individualEnabled =
- mIndividualEnabled.get(userId);
- if (individualEnabled != null) {
- individualEnabled.delete(sensor);
- }
- persistSensorPrivacyState();
- }
- }
- break;
default:
return handleDefaultCommands(cmd);
}
@@ -1545,9 +1184,6 @@
pw.println(" disable USER_ID SENSOR");
pw.println(" Disable privacy for a certain sensor.");
pw.println("");
- pw.println(" reset USER_ID SENSOR");
- pw.println(" Reset privacy state for a certain sensor.");
- pw.println("");
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -1581,22 +1217,6 @@
mContext = context;
}
- public void onSensorPrivacyChanged(boolean enabled) {
- sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
- this, enabled));
- sendMessage(
- PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
- mSensorPrivacyServiceImpl));
- }
-
- public void onSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
- sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
- this, userId, sensor, enabled));
- sendMessage(
- PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
- mSensorPrivacyServiceImpl));
- }
-
public void onUserGlobalSensorPrivacyChanged(int sensor, boolean enabled) {
sendMessage(PooledLambda.obtainMessage(
SensorPrivacyHandler::handleUserGlobalSensorPrivacyChanged,
@@ -1692,6 +1312,7 @@
}
public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
+ // TODO handle hardware
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
@@ -1972,11 +1593,7 @@
}
}
- private static long getCurrentTimeMillis() {
- try {
- return SystemClock.currentNetworkTimeMillis();
- } catch (Exception e) {
- return System.currentTimeMillis();
- }
+ static long getCurrentTimeMillis() {
+ return SystemClock.elapsedRealtime();
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
new file mode 100644
index 0000000..9694958
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
@@ -0,0 +1,158 @@
+/*
+ * 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.sensorprivacy;
+
+import android.os.Handler;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+abstract class SensorPrivacyStateController {
+
+ private static SensorPrivacyStateController sInstance;
+
+ AllSensorStateController mAllSensorStateController = AllSensorStateController.getInstance();
+
+ private final Object mLock = new Object();
+
+ static SensorPrivacyStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = SensorPrivacyStateControllerImpl.getInstance();
+ }
+
+ return sInstance;
+ }
+
+ SensorState getState(int toggleType, int userId, int sensor) {
+ synchronized (mLock) {
+ return getStateLocked(toggleType, userId, sensor);
+ }
+ }
+
+ void setState(int toggleType, int userId, int sensor, boolean enabled, Handler callbackHandler,
+ SetStateResultCallback callback) {
+ synchronized (mLock) {
+ setStateLocked(toggleType, userId, sensor, enabled, callbackHandler, callback);
+ }
+ }
+
+ void setSensorPrivacyListener(Handler handler,
+ SensorPrivacyListener listener) {
+ synchronized (mLock) {
+ setSensorPrivacyListenerLocked(handler, listener);
+ }
+ }
+
+ // Following calls are for the developer settings sensor mute feature
+ boolean getAllSensorState() {
+ synchronized (mLock) {
+ return mAllSensorStateController.getAllSensorStateLocked();
+ }
+ }
+
+ void setAllSensorState(boolean enable) {
+ synchronized (mLock) {
+ mAllSensorStateController.setAllSensorStateLocked(enable);
+ }
+ }
+
+ void setAllSensorPrivacyListener(Handler handler, AllSensorPrivacyListener listener) {
+ synchronized (mLock) {
+ mAllSensorStateController.setAllSensorPrivacyListenerLocked(handler, listener);
+ }
+ }
+
+ void persistAll() {
+ synchronized (mLock) {
+ mAllSensorStateController.schedulePersistLocked();
+ schedulePersistLocked();
+ }
+ }
+
+ void forEachState(SensorPrivacyStateConsumer consumer) {
+ synchronized (mLock) {
+ forEachStateLocked(consumer);
+ }
+ }
+
+ void dump(DualDumpOutputStream dumpStream) {
+ synchronized (mLock) {
+ mAllSensorStateController.dumpLocked(dumpStream);
+ dumpLocked(dumpStream);
+ }
+ dumpStream.flush();
+ }
+
+ public void atomic(Runnable r) {
+ synchronized (mLock) {
+ r.run();
+ }
+ }
+
+ interface SensorPrivacyListener {
+ void onSensorPrivacyChanged(int toggleType, int userId, int sensor, SensorState state);
+ }
+
+ interface SensorPrivacyStateConsumer {
+ void accept(int toggleType, int userId, int sensor, SensorState state);
+ }
+
+ interface SetStateResultCallback {
+ void callback(boolean changed);
+ }
+
+ interface AllSensorPrivacyListener {
+ void onAllSensorPrivacyChanged(boolean enabled);
+ }
+
+ @GuardedBy("mLock")
+ abstract SensorState getStateLocked(int toggleType, int userId, int sensor);
+
+ @GuardedBy("mLock")
+ abstract void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
+ Handler callbackHandler, SetStateResultCallback callback);
+
+ @GuardedBy("mLock")
+ abstract void setSensorPrivacyListenerLocked(Handler handler,
+ SensorPrivacyListener listener);
+
+ @GuardedBy("mLock")
+ abstract void schedulePersistLocked();
+
+ @GuardedBy("mLock")
+ abstract void forEachStateLocked(SensorPrivacyStateConsumer consumer);
+
+ @GuardedBy("mLock")
+ abstract void dumpLocked(DualDumpOutputStream dumpStream);
+
+ static void sendSetStateCallback(Handler callbackHandler,
+ SetStateResultCallback callback, boolean success) {
+ callbackHandler.sendMessage(PooledLambda.obtainMessage(SetStateResultCallback::callback,
+ callback, success));
+ }
+
+ /**
+ * Used for unit testing
+ */
+ void resetForTesting() {
+ mAllSensorStateController.resetForTesting();
+ resetForTestingImpl();
+ sInstance = null;
+ }
+ abstract void resetForTestingImpl();
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
new file mode 100644
index 0000000..d1ea8e9
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sensorprivacy;
+
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.Objects;
+
+class SensorPrivacyStateControllerImpl extends SensorPrivacyStateController {
+
+ private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy_impl.xml";
+
+ private static SensorPrivacyStateControllerImpl sInstance;
+
+ private PersistedState mPersistedState;
+
+ private SensorPrivacyListener mListener;
+ private Handler mListenerHandler;
+
+ static SensorPrivacyStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = new SensorPrivacyStateControllerImpl();
+ }
+ return sInstance;
+ }
+
+ private SensorPrivacyStateControllerImpl() {
+ mPersistedState = PersistedState.fromFile(SENSOR_PRIVACY_XML_FILE);
+ persistAll();
+ }
+
+ @Override
+ SensorState getStateLocked(int toggleType, int userId, int sensor) {
+ if (toggleType == SensorPrivacyManager.ToggleTypes.HARDWARE) {
+ // Device doesn't support hardware state
+ return getDefaultSensorState();
+ }
+ SensorState sensorState = mPersistedState.getState(toggleType, userId, sensor);
+ if (sensorState != null) {
+ return new SensorState(sensorState);
+ }
+ return getDefaultSensorState();
+ }
+
+ private static SensorState getDefaultSensorState() {
+ return new SensorState(false);
+ }
+
+ @Override
+ void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
+ Handler callbackHandler, SetStateResultCallback callback) {
+ if (toggleType != SensorPrivacyManager.ToggleTypes.SOFTWARE) {
+ // Implementation only supports software switch
+ callbackHandler.sendMessage(PooledLambda.obtainMessage(
+ SetStateResultCallback::callback, callback, false));
+ return;
+ }
+ // Changing the SensorState's mEnabled updates the timestamp of its last change.
+ // A nonexistent state -> unmuted should not set the timestamp.
+ SensorState lastState = mPersistedState.getState(toggleType, userId, sensor);
+ if (lastState == null) {
+ if (!enabled) {
+ sendSetStateCallback(callbackHandler, callback, false);
+ return;
+ } else if (enabled) {
+ SensorState sensorState = new SensorState(true);
+ mPersistedState.setState(toggleType, userId, sensor, sensorState);
+ notifyStateChangeLocked(toggleType, userId, sensor, sensorState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ }
+ if (lastState.setEnabled(enabled)) {
+ notifyStateChangeLocked(toggleType, userId, sensor, lastState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ sendSetStateCallback(callbackHandler, callback, false);
+ }
+
+ private void notifyStateChangeLocked(int toggleType, int userId, int sensor,
+ SensorState sensorState) {
+ if (mListenerHandler != null && mListener != null) {
+ mListenerHandler.sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyListener::onSensorPrivacyChanged, mListener,
+ toggleType, userId, sensor, new SensorState(sensorState)));
+ }
+ schedulePersistLocked();
+ }
+
+ @Override
+ void setSensorPrivacyListenerLocked(Handler handler, SensorPrivacyListener listener) {
+ Objects.requireNonNull(handler);
+ Objects.requireNonNull(listener);
+ if (mListener != null) {
+ throw new IllegalStateException("Listener is already set");
+ }
+ mListener = listener;
+ mListenerHandler = handler;
+ }
+
+ @Override
+ void schedulePersistLocked() {
+ mPersistedState.schedulePersist();
+ }
+
+ @Override
+ void forEachStateLocked(SensorPrivacyStateConsumer consumer) {
+ mPersistedState.forEachKnownState(consumer::accept);
+ }
+
+ @Override
+ void resetForTestingImpl() {
+ mPersistedState.resetForTesting();
+ mListener = null;
+ mListenerHandler = null;
+ sInstance = null;
+ }
+
+ @Override
+ void dumpLocked(DualDumpOutputStream dumpStream) {
+ mPersistedState.dump(dumpStream);
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorState.java b/services/core/java/com/android/server/sensorprivacy/SensorState.java
new file mode 100644
index 0000000..b92e2c8
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorState.java
@@ -0,0 +1,79 @@
+/*
+ * 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.sensorprivacy;
+
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+
+import static com.android.server.sensorprivacy.SensorPrivacyService.getCurrentTimeMillis;
+
+class SensorState {
+
+ private int mStateType;
+ private long mLastChange;
+
+ SensorState(int stateType) {
+ mStateType = stateType;
+ mLastChange = getCurrentTimeMillis();
+ }
+
+ SensorState(int stateType, long lastChange) {
+ mStateType = stateType;
+ mLastChange = Math.min(getCurrentTimeMillis(), lastChange);
+ }
+
+ SensorState(SensorState sensorState) {
+ mStateType = sensorState.getState();
+ mLastChange = sensorState.getLastChange();
+ }
+
+ boolean setState(int stateType) {
+ if (mStateType != stateType) {
+ mStateType = stateType;
+ mLastChange = getCurrentTimeMillis();
+ return true;
+ }
+ return false;
+ }
+
+ int getState() {
+ return mStateType;
+ }
+
+ long getLastChange() {
+ return mLastChange;
+ }
+
+ // Below are some convenience members for when dealing with enabled/disabled
+
+ private static int enabledToState(boolean enabled) {
+ return enabled ? ENABLED : DISABLED;
+ }
+
+ SensorState(boolean enabled) {
+ this(enabledToState(enabled));
+ }
+
+ boolean setEnabled(boolean enabled) {
+ return setState(enabledToState(enabled));
+ }
+
+ boolean isEnabled() {
+ return getState() == ENABLED;
+ }
+
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 030bbd2..5636718 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -70,7 +70,7 @@
* cleanup in response to a single binder operation, it should not be used to propagate
* errors further. Run on the ServiceWatcher thread.
*/
- default void onError() {}
+ default void onError(Throwable t) {}
}
/**
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index 631be38..94ea463 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -239,7 +240,7 @@
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBinder == null) {
- operation.onError();
+ operation.onError(new DeadObjectException());
return;
}
@@ -249,7 +250,7 @@
// binders may propagate some specific non-RemoteExceptions from the other side
// through the binder as well - we cannot allow those to crash the system server
Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
- operation.onError();
+ operation.onError(e);
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 277d802..4cfe3d3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -30,7 +30,6 @@
import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
import static android.net.NetworkTemplate.OEM_MANAGED_PAID;
import static android.net.NetworkTemplate.OEM_MANAGED_PRIVATE;
-import static android.net.NetworkTemplate.getAllCollapsedRatTypes;
import static android.os.Debug.getIonHeapsSizeKb;
import static android.os.Process.LAST_SHARED_APPLICATION_GID;
import static android.os.Process.getUidForPid;
@@ -1190,13 +1189,14 @@
Slog.e(TAG, "baseline is null for " + atomTag + ", return.");
return StatsManager.PULL_SKIP;
}
+
final NetworkStatsExt diff = new NetworkStatsExt(
- item.stats.subtract(baseline.stats).removeEmptyEntries(), item.transports,
+ removeEmptyEntries(item.stats.subtract(baseline.stats)), item.transports,
item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType,
item.subInfo, item.oemManaged);
// If no diff, skip.
- if (diff.stats.size() == 0) continue;
+ if (!diff.stats.iterator().hasNext()) continue;
switch (atomTag) {
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
@@ -1215,6 +1215,17 @@
return StatsManager.PULL_SUCCESS;
}
+ @NonNull private static NetworkStats removeEmptyEntries(NetworkStats stats) {
+ NetworkStats ret = new NetworkStats(0, 1);
+ for (NetworkStats.Entry e : stats) {
+ if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
+ || e.getTxPackets() != 0 || e.getOperations() != 0) {
+ ret = ret.addEntry(e);
+ }
+ }
+ return ret;
+ }
+
private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@NonNull NetworkStatsExt statsExt) {
for (NetworkStats.Entry entry : statsExt.stats) {
@@ -1249,11 +1260,11 @@
private void addDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
@NonNull List<StatsEvent> pulledData) {
- // Workaround for 5G NSA mode, see {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}.
+ // Workaround for 5G NSA mode, see {@link NetworkStatsManager#NETWORK_TYPE_5G_NSA}.
// 5G NSA mode means the primary cell is LTE with a secondary connection to an
// NR cell. To mitigate risk, NetworkStats is currently storing this state as
// a fake RAT type rather than storing the boolean separately.
- final boolean is5GNsa = statsExt.ratType == NetworkTemplate.NETWORK_TYPE_5G_NSA;
+ final boolean is5GNsa = statsExt.ratType == NetworkStatsManager.NETWORK_TYPE_5G_NSA;
// Report NR connected in 5G non-standalone mode, or if the RAT type is NR to begin with.
final boolean isNR = is5GNsa || statsExt.ratType == TelephonyManager.NETWORK_TYPE_NR;
@@ -1399,6 +1410,27 @@
return ret;
}
+ /**
+ * Return all supported collapsed RAT types that could be returned by
+ * {@link android.app.usage.NetworkStatsManager#getCollapsedRatType(int)}.
+ */
+ @NonNull
+ private static int[] getAllCollapsedRatTypes() {
+ final int[] ratTypes = TelephonyManager.getAllNetworkTypes();
+ final HashSet<Integer> collapsedRatTypes = new HashSet<>();
+ for (final int ratType : ratTypes) {
+ collapsedRatTypes.add(NetworkStatsManager.getCollapsedRatType(ratType));
+ }
+ // Add NETWORK_TYPE_5G_NSA to the returned list since 5G NSA is a virtual RAT type and
+ // it is not in TelephonyManager#NETWORK_TYPE_* constants.
+ // See {@link NetworkStatsManager#NETWORK_TYPE_5G_NSA}.
+ collapsedRatTypes.add(
+ NetworkStatsManager.getCollapsedRatType(NetworkStatsManager.NETWORK_TYPE_5G_NSA));
+ // Ensure that unknown type is returned.
+ collapsedRatTypes.add(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ return com.android.net.module.util.CollectionUtils.toIntArray(collapsedRatTypes);
+ }
+
@NonNull private NetworkStats sliceNetworkStatsByUid(@NonNull NetworkStats stats) {
return sliceNetworkStats(stats,
(entry) -> {
@@ -1472,12 +1504,8 @@
@NonNull private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
@NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
NetworkStats ret = new NetworkStats(0, 1);
- NetworkStats.Entry entry = new NetworkStats.Entry();
for (NetworkStats.Entry e : stats) {
- if (slicer != null) {
- entry = slicer.apply(e);
- }
- ret = ret.addEntry(entry);
+ ret = ret.addEntry(slicer.apply(e));
}
return ret;
}
diff --git a/services/core/java/com/android/server/statusbar/SessionMonitor.java b/services/core/java/com/android/server/statusbar/SessionMonitor.java
new file mode 100644
index 0000000..f4356bd
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/SessionMonitor.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.statusbar;
+
+import static android.app.StatusBarManager.ALL_SESSIONS;
+import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.app.StatusBarManager.SessionFlags;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors session starts and ends. Session instanceIds can be used to correlate logs.
+ */
+public class SessionMonitor {
+ private static final String TAG = "SessionMonitor";
+
+ private final Context mContext;
+ private final Map<Integer, Set<ISessionListener>> mSessionToListeners =
+ new HashMap<>();
+
+ /** */
+ public SessionMonitor(Context context) {
+ mContext = context;
+ // initialize all sessions in the map
+ for (int session : ALL_SESSIONS) {
+ mSessionToListeners.put(session, new HashSet<>());
+ }
+ }
+
+ /**
+ * Registers a listener for all sessionTypes included in sessionFlags.
+ */
+ public void registerSessionListener(@SessionFlags int sessionFlags,
+ ISessionListener listener) {
+ requireListenerPermissions(sessionFlags);
+ synchronized (mSessionToListeners) {
+ for (int sessionType : ALL_SESSIONS) {
+ if ((sessionFlags & sessionType) != 0) {
+ mSessionToListeners.get(sessionType).add(listener);
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregisters a listener for all sessionTypes included in sessionFlags.
+ */
+ public void unregisterSessionListener(@SessionFlags int sessionFlags,
+ ISessionListener listener) {
+ synchronized (mSessionToListeners) {
+ for (int sessionType : ALL_SESSIONS) {
+ if ((sessionFlags & sessionType) != 0) {
+ mSessionToListeners.get(sessionType).remove(listener);
+ }
+ }
+ }
+ }
+
+ /**
+ * Starts a session with the given sessionType, creating a new instanceId.
+ * Sends this message to all listeners registered for the given sessionType.
+ *
+ * Callers require special permission to start and end a session depending on the session.
+ */
+ public void onSessionStarted(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
+ requireSetterPermissions(sessionType);
+
+ if (!isValidSessionType(sessionType)) {
+ Log.e(TAG, "invalid onSessionStarted sessionType=" + sessionType);
+ return;
+ }
+
+ synchronized (mSessionToListeners) {
+ for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
+ try {
+ listener.onSessionStarted(sessionType, instanceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "unable to send session start to listener=" + listener, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Ends a session with the given sessionType and instanceId. Sends this message
+ * to all listeners registered for the given sessionType.
+ *
+ * Callers require special permission to start and end a session depending on the session.
+ */
+ public void onSessionEnded(@SessionFlags int sessionType, @NonNull InstanceId instanceId) {
+ requireSetterPermissions(sessionType);
+
+ if (!isValidSessionType(sessionType)) {
+ Log.e(TAG, "invalid onSessionEnded sessionType=" + sessionType);
+ return;
+ }
+
+ synchronized (mSessionToListeners) {
+ for (ISessionListener listener : mSessionToListeners.get(sessionType)) {
+ try {
+ listener.onSessionEnded(sessionType, instanceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "unable to send session end to listener=" + listener, e);
+ }
+ }
+ }
+ }
+
+ private boolean isValidSessionType(@SessionFlags int sessionType) {
+ return ALL_SESSIONS.contains(sessionType);
+ }
+
+ private void requireListenerPermissions(@SessionFlags int sessionFlags) {
+ if ((sessionFlags & SESSION_KEYGUARD) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIOMETRIC,
+ "StatusBarManagerService.SessionMonitor");
+ }
+
+ if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIOMETRIC,
+ "StatusBarManagerService.SessionMonitor");
+ }
+ }
+
+ private void requireSetterPermissions(@SessionFlags int sessionFlags) {
+ if ((sessionFlags & SESSION_KEYGUARD) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_KEYGUARD,
+ "StatusBarManagerService.SessionMonitor");
+ }
+
+ if ((sessionFlags & SESSION_BIOMETRIC_PROMPT) != 0) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ "StatusBarManagerService.SessionMonitor");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0edd06a..e71ff78 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -21,6 +21,7 @@
import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
import static android.app.StatusBarManager.NavBarModeOverride;
+import static android.app.StatusBarManager.SessionFlags;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
@@ -84,8 +85,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.logging.InstanceId;
import com.android.internal.os.TransferPipe;
import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -145,6 +148,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManager;
private final PackageManagerInternal mPackageManagerInternal;
+ private final SessionMonitor mSessionMonitor;
private int mCurrentUserId;
private boolean mTracingEnabled;
@@ -260,6 +264,7 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mTileRequestTracker = new TileRequestTracker(mContext);
+ mSessionMonitor = new SessionMonitor(mContext);
}
private IOverlayManager getOverlayManager() {
@@ -1870,6 +1875,28 @@
}
}
+ @Override
+ public void onSessionStarted(@SessionFlags int sessionType, InstanceId instance) {
+ mSessionMonitor.onSessionStarted(sessionType, instance);
+ }
+
+ @Override
+ public void onSessionEnded(@SessionFlags int sessionType, InstanceId instance) {
+ mSessionMonitor.onSessionEnded(sessionType, instance);
+ }
+
+ @Override
+ public void registerSessionListener(@SessionFlags int sessionFlags,
+ ISessionListener listener) {
+ mSessionMonitor.registerSessionListener(sessionFlags, listener);
+ }
+
+ @Override
+ public void unregisterSessionListener(@SessionFlags int sessionFlags,
+ ISessionListener listener) {
+ mSessionMonitor.unregisterSessionListener(sessionFlags, listener);
+ }
+
public String[] getStatusBarIcons() {
return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 897549b..c0eee61 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6568,7 +6568,8 @@
return null;
}
- private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity) {
+ private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity,
+ ActivityOptions options) {
if (sourceRecord == null && !startActivity) {
// Use empty style if this activity is not top activity. This could happen when adding
// a splash screen window to the warm start activity which is re-create because top is
@@ -6578,8 +6579,8 @@
return true;
}
}
- if (mPendingOptions != null) {
- final int optionsStyle = mPendingOptions.getSplashScreenStyle();
+ if (options != null) {
+ final int optionsStyle = options.getSplashScreenStyle();
if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) {
return true;
} else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) {
@@ -6608,11 +6609,11 @@
|| mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME);
}
- private int getSplashscreenTheme() {
+ private int getSplashscreenTheme(ActivityOptions options) {
// Find the splash screen theme. User can override the persisted theme by
// ActivityOptions.
- String splashScreenThemeResName = mPendingOptions != null
- ? mPendingOptions.getSplashScreenThemeResName() : null;
+ String splashScreenThemeResName = options != null
+ ? options.getSplashScreenThemeResName() : null;
if (splashScreenThemeResName == null || splashScreenThemeResName.isEmpty()) {
try {
splashScreenThemeResName = mAtmService.getPackageManager()
@@ -6639,7 +6640,7 @@
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean startActivity, ActivityRecord sourceRecord) {
showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
- sourceRecord);
+ sourceRecord, null /* candidateOptions */);
}
/**
@@ -6647,22 +6648,27 @@
* @param processRunning Whether the client process is running.
* @param startActivity Whether this activity is just created from starter.
* @param sourceRecord The source activity which start this activity.
+ * @param candidateOptions The options for the style of starting window.
*/
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
- boolean processRunning, boolean startActivity, ActivityRecord sourceRecord) {
+ boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
+ ActivityOptions candidateOptions) {
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
}
- if (mPendingOptions != null
- && mPendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ final ActivityOptions startOptions = candidateOptions != null
+ ? candidateOptions : mPendingOptions;
+ if (startOptions != null
+ && startOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
// Don't show starting window when using shared element transition.
return;
}
- mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(sourceRecord, startActivity);
+ mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(
+ sourceRecord, startActivity, startOptions);
- final int splashScreenTheme = startActivity ? getSplashscreenTheme() : 0;
+ final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;
final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
splashScreenTheme);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 87fb290..2a26050 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -484,7 +484,8 @@
}
}
} finally {
- mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow();
+ mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow(
+ options != null ? options.getOriginalOptions() : null);
mService.continueWindowLayout();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ed9dcef..8d4ae90 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3959,6 +3959,11 @@
getActivityStartController().dump(pw, "", dumpPackage);
}
+ /** Dumps installed packages having app-specific config. */
+ void dumpInstalledPackagesConfig(PrintWriter pw) {
+ mPackageConfigPersister.dump(pw, getCurrentUserId());
+ }
+
/**
* There are three things that cmd can be:
* - a flattened component name that matches an existing activity
@@ -6624,9 +6629,11 @@
@Override
public void notifyWakingUp() {
- // Start a transition for waking. This is needed for showWhenLocked activities.
- getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
- null /* trigger */, mRootWindowContainer.getDefaultDisplay());
+ synchronized (mGlobalLock) {
+ // Start a transition for waking. This is needed for showWhenLocked activities.
+ getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
+ null /* trigger */, mRootWindowContainer.getDefaultDisplay());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5d879ce..3d7dead 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -906,8 +906,7 @@
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
- r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
- r.getLaunchedFromBubble()));
+ r.shareableActivityToken, r.getLaunchedFromBubble()));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a1c823e..4009220 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -170,7 +170,6 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
-import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -1684,9 +1683,6 @@
// adjustments of previous rotated activity should be cleared earlier. Otherwise if
// the current top is in the same process, it may get the rotated state. The transform
// will be cleared later with transition callback to ensure smooth animation.
- if (hasTopFixedRotationLaunchingApp()) {
- mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
- }
return false;
}
if (!r.getDisplayArea().matchParentBounds()) {
@@ -2191,8 +2187,8 @@
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,
- dh);
+ outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh);
+ outConfig.windowConfiguration.setDisplayRotation(rotation);
}
/**
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 0e2d847..8db4306 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -41,6 +41,8 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
/* maps input token to an embedded window */
private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
+ private ArrayMap<IBinder /*focus grant token */, EmbeddedWindow> mWindowsByFocusToken =
+ new ArrayMap<>();
private final Object mGlobalLock;
private final ActivityTaskManagerService mAtmService;
@@ -59,10 +61,13 @@
void add(IBinder inputToken, EmbeddedWindow window) {
try {
mWindows.put(inputToken, window);
+ final IBinder focusToken = window.getFocusGrantToken();
+ mWindowsByFocusToken.put(focusToken, window);
updateProcessController(window);
window.mClient.asBinder().linkToDeath(()-> {
synchronized (mGlobalLock) {
mWindows.remove(inputToken);
+ mWindowsByFocusToken.remove(focusToken);
}
}, 0);
} catch (RemoteException e) {
@@ -98,8 +103,8 @@
return embeddedWindow != null ? embeddedWindow.getIsOverlay() : false;
}
- void setIsOverlay(IBinder inputToken) {
- EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
+ void setIsOverlay(IBinder focusGrantToken) {
+ EmbeddedWindow embeddedWindow = mWindowsByFocusToken.get(focusGrantToken);
if (embeddedWindow != null) {
embeddedWindow.setIsOverlay();
}
@@ -107,8 +112,10 @@
void remove(IWindow client) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
- if (mWindows.valueAt(i).mClient.asBinder() == client.asBinder()) {
+ EmbeddedWindow ew = mWindows.valueAt(i);
+ if (ew.mClient.asBinder() == client.asBinder()) {
mWindows.removeAt(i).onRemoved();
+ mWindowsByFocusToken.remove(ew.getFocusGrantToken());
return;
}
}
@@ -116,8 +123,10 @@
void onWindowRemoved(WindowState host) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
- if (mWindows.valueAt(i).mHostWindowState == host) {
+ EmbeddedWindow ew = mWindows.valueAt(i);
+ if (ew.mHostWindowState == host) {
mWindows.removeAt(i).onRemoved();
+ mWindowsByFocusToken.remove(ew.getFocusGrantToken());
}
}
}
@@ -126,6 +135,10 @@
return mWindows.get(inputToken);
}
+ EmbeddedWindow getByFocusToken(IBinder focusGrantToken) {
+ return mWindowsByFocusToken.get(focusGrantToken);
+ }
+
void onActivityRemoved(ActivityRecord activityRecord) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
final EmbeddedWindow window = mWindows.valueAt(i);
@@ -157,6 +170,8 @@
// and this variable is mostly used for tracking that.
boolean mIsOverlay = false;
+ private IBinder mFocusGrantToken;
+
/**
* @param session calling session to check ownership of the window
* @param clientToken client token used to clean up the map if the embedding process dies
@@ -171,7 +186,7 @@
*/
EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
- int displayId) {
+ int displayId, IBinder focusGrantToken) {
mSession = session;
mWmService = service;
mClient = clientToken;
@@ -182,6 +197,7 @@
mOwnerPid = ownerPid;
mWindowType = windowType;
mDisplayId = displayId;
+ mFocusGrantToken = focusGrantToken;
}
@Override
@@ -242,6 +258,17 @@
return mIsOverlay;
}
+ IBinder getFocusGrantToken() {
+ return mFocusGrantToken;
+ }
+
+ IBinder getInputChannelToken() {
+ if (mInputChannel != null) {
+ return mInputChannel.getToken();
+ }
+ return null;
+ }
+
/**
* System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and
* so we need to participate inside handlePointerDownOutsideFocus logic
@@ -255,7 +282,7 @@
private void handleTap(boolean grantFocus) {
if (mInputChannel != null) {
- mWmService.grantEmbeddedWindowFocus(mSession, mInputChannel.getToken(), grantFocus);
+ mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 9326a2e..10776ab 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -219,13 +219,23 @@
/**
* @see InsetsStateController#getInsetsForWindow
*/
- InsetsState getInsetsForWindow(WindowState target) {
+ InsetsState getInsetsForWindow(WindowState target, boolean includesTransient) {
final InsetsState originalState = mStateController.getInsetsForWindow(target);
- InsetsState state = adjustVisibilityForTransientTypes(originalState);
+ InsetsState state;
+ if (!includesTransient) {
+ state = adjustVisibilityForTransientTypes(originalState);
+ } else {
+ state = originalState;
+ }
state = adjustVisibilityForIme(target, state, state == originalState);
return adjustInsetsForRoundedCorners(target, state, state == originalState);
}
+ InsetsState getInsetsForWindow(WindowState target) {
+ return getInsetsForWindow(target, false);
+ }
+
+
/**
* @see InsetsStateController#getInsetsForWindowMetrics
*/
diff --git a/services/core/java/com/android/server/wm/OverlayHost.java b/services/core/java/com/android/server/wm/OverlayHost.java
index 724e124..90f5b09 100644
--- a/services/core/java/com/android/server/wm/OverlayHost.java
+++ b/services/core/java/com/android/server/wm/OverlayHost.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -121,6 +123,16 @@
}
}
+ void dispatchInsetsChanged(InsetsState s, Rect insetFrame) {
+ for (int i = mOverlays.size() - 1; i >= 0; i--) {
+ SurfaceControlViewHost.SurfacePackage l = mOverlays.get(i);
+ try {
+ l.getRemoteInterface().onInsetsChanged(s, insetFrame);
+ } catch (Exception e) {
+ }
+ }
+ }
+
void release() {
dispatchDetachedFromWindow();
mOverlays.clear();
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 7a7fb65..16f4377 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -40,6 +40,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.util.HashMap;
/**
@@ -308,6 +309,27 @@
}
}
+ /**
+ * Dumps app-specific configurations for all packages for which the records
+ * exist.
+ */
+ void dump(PrintWriter pw, int userId) {
+ pw.println("INSTALLED PACKAGES HAVING APP-SPECIFIC CONFIGURATIONS");
+ pw.println("Current user ID : " + userId);
+ synchronized (mLock) {
+ HashMap<String, PackageConfigRecord> persistedPackageConfigMap = mModified.get(userId);
+ if (persistedPackageConfigMap != null) {
+ for (PackageConfigPersister.PackageConfigRecord packageConfig
+ : persistedPackageConfigMap.values()) {
+ pw.println();
+ pw.println(" PackageName : " + packageConfig.mName);
+ pw.println(" NightMode : " + packageConfig.mNightMode);
+ pw.println(" Locales : " + packageConfig.mLocales);
+ }
+ }
+ }
+ }
+
// store a changed data so we don't need to get the process
static class PackageConfigRecord {
final String mName;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9b94f44..98acc46 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -813,7 +813,7 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
- InputChannel outInputChannel) {
+ IBinder focusGrantToken, InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
// Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
// embedded windows without providing a host window input token
@@ -829,7 +829,7 @@
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- mCanAddInternalSystemWindow ? type : 0, outInputChannel);
+ mCanAddInternalSystemWindow ? type : 0, focusGrantToken, outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 2a3767f..58091c8 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -215,12 +216,12 @@
deferring, prev, source));
}
- private void showStartingWindowFromDeferringActivities() {
+ private void showStartingWindowFromDeferringActivities(ActivityOptions topOptions) {
// Attempt to add starting window from the top-most activity.
for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
- mInitProcessRunning, true /* startActivity */, next.mSource);
+ mInitProcessRunning, true /* startActivity */, next.mSource, topOptions);
// If one succeeds, it is done.
if (next.mDeferring.mStartingData != null) {
break;
@@ -243,9 +244,9 @@
/**
* End deferring add starting window.
*/
- void endDeferAddStartingWindow() {
+ void endDeferAddStartingWindow(ActivityOptions topOptions) {
mDeferringAddStartingWindow = false;
- showStartingWindowFromDeferringActivities();
+ showStartingWindowFromDeferringActivities(topOptions);
}
final class StartingSurface {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 50c9b31..53e3378 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -88,6 +88,8 @@
private boolean mAnimationStartDelayed;
+ private boolean mAnimationFinished;
+
/**
* @param animatable The object to animate.
* @param staticAnimationFinishedCallback Callback to invoke when an animation has finished
@@ -137,6 +139,7 @@
|| anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
resetAndInvokeFinish.run();
}
+ mAnimationFinished = true;
}
};
}
@@ -302,6 +305,9 @@
Slog.w(TAG, "Unable to transfer animation, surface or parent is null");
cancelAnimation();
return;
+ } else if (from.mAnimationFinished) {
+ Slog.w(TAG, "Unable to transfer animation, because " + from + " animation is finished");
+ return;
}
endDelayingAnimationStart();
final Transaction t = mAnimatable.getPendingTransaction();
@@ -392,6 +398,7 @@
SurfaceControl leash = mLeash;
mLeash = null;
final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash);
+ mAnimationFinished = false;
if (scheduleAnim) {
mService.scheduleAnimationLocked();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 91c1374..7d06526 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6588,4 +6588,18 @@
mLaunchCookie, mDeferTaskAppear, mRemoveWithTaskOrganizer);
}
}
+
+ @Override
+ void updateOverlayInsetsState(WindowState originalChange) {
+ super.updateOverlayInsetsState(originalChange);
+ if (originalChange != getTopVisibleAppMainWindow()) {
+ return;
+ }
+ if (mOverlayHost != null) {
+ final InsetsState s = getDisplayContent().getInsetsPolicy()
+ .getInsetsForWindow(originalChange, true);
+ getBounds(mTmpRect);
+ mOverlayHost.dispatchInsetsChanged(s, mTmpRect);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 11d1983..1bd153b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,6 +81,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
+import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
@@ -313,7 +314,7 @@
private final List<WindowContainerListener> mListeners = new ArrayList<>();
- private OverlayHost mOverlayHost;
+ protected OverlayHost mOverlayHost;
WindowContainer(WindowManagerService wms) {
mWmService = wms;
@@ -3604,6 +3605,16 @@
mOverlayHost = new OverlayHost(mWmService);
}
mOverlayHost.addOverlay(overlay, mSurfaceControl);
+
+ // Emit an initial onConfigurationChanged to ensure the overlay
+ // can receive any changes between their creation time and
+ // attach time.
+ try {
+ overlay.getRemoteInterface().onConfigurationChanged(getConfiguration());
+ } catch (Exception e) {
+ Slog.e(TAG, "Error sending initial configuration change to WindowContainer overlay");
+ removeOverlay(overlay);
+ }
}
void removeOverlay(SurfaceControlViewHost.SurfacePackage overlay) {
@@ -3612,4 +3623,11 @@
mOverlayHost = null;
}
}
+
+ void updateOverlayInsetsState(WindowState originalChange) {
+ final WindowContainer p = getParent();
+ if (p != null) {
+ p.updateOverlayInsetsState(originalChange);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index b9fa297..20fa7a9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -35,6 +35,7 @@
import android.view.InputChannel;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
@@ -82,7 +83,7 @@
* through the tracing file.
* @param loggingTypeFlags The flags for the logging types this log entry belongs to.
* @param callingParams The parameters for the method to be logged.
- * @param a11yDump The proto byte array for a11y state when the entry is generated.
+ * @param a11yDump The proto byte array for a11y state when the entry is generated
* @param callingUid The calling uid.
* @param stackTrace The stack trace, null if not needed.
* @param ignoreStackEntries The stack entries can be removed
@@ -796,8 +797,19 @@
* Callers prepare a view hierarchy with SurfaceControlViewHost
* and send the package to WM here. The remote view hierarchy will receive
* configuration change, lifecycle events, etc, forwarded over the
- * ISurfaceControlViewHost interface inside the SurfacePackage.
+ * ISurfaceControlViewHost interface inside the SurfacePackage. Embedded
+ * hierarchies will receive inset changes, including transient inset changes
+ * (to avoid the status bar in immersive mode).
+ *
+ * The embedded hierarchy exists in a coordinate space relative to the task
+ * bounds.
*/
public abstract void addTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
public abstract void removeTaskOverlay(int taskId, SurfaceControlViewHost.SurfacePackage overlay);
+
+ /**
+ * Get a SurfaceControl that is the container layer that should be used to receive input to
+ * support handwriting (Scribe) by the IME.
+ */
+ public abstract SurfaceControl getHandwritingSurfaceForDisplay(int displayId);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index eb1274c..b37cb4f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6622,6 +6622,7 @@
pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
+ pw.println(" package-config: installed packages having app-specific config");
pw.println(" trace: print trace status and write Winscope trace to file");
pw.println(" cmd may also be a NAME to dump windows. NAME may");
pw.println(" be a partial substring in a window name, a");
@@ -6708,6 +6709,9 @@
} else if ("constants".equals(cmd)) {
mConstants.dump(pw);
return;
+ } else if ("package-config".equals(cmd)) {
+ mAtmService.dumpInstalledPackagesConfig(pw);
+ return;
} else {
// Dumping a single name?
if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
@@ -6774,6 +6778,10 @@
if (dumpAll) {
pw.println(separator);
}
+ mAtmService.dumpInstalledPackagesConfig(pw);
+ if (dumpAll) {
+ pw.println(separator);
+ }
mConstants.dump(pw);
}
}
@@ -7949,6 +7957,25 @@
task.removeOverlay(overlay);
}
}
+
+ @Override
+ public SurfaceControl getHandwritingSurfaceForDisplay(int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.e(TAG, "Failed to create a handwriting surface on display: "
+ + displayId + " - DisplayContent not found.");
+ return null;
+ }
+ //TODO (b/210039666): Use a method like add/removeDisplayOverlay if available.
+ return makeSurfaceBuilder(dc.getSession())
+ .setContainerLayer()
+ .setName("IME Handwriting Surface")
+ .setCallsite("getHandwritingSurfaceForDisplay")
+ .setParent(dc.getSurfaceControl())
+ .build();
+ }
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -8276,7 +8303,8 @@
*/
void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
SurfaceControl surface, IWindow window, IBinder hostInputToken,
- int flags, int privateFlags, int type, InputChannel outInputChannel) {
+ int flags, int privateFlags, int type, IBinder focusGrantToken,
+ InputChannel outInputChannel) {
final InputApplicationHandle applicationHandle;
final String name;
final InputChannel clientChannel;
@@ -8284,7 +8312,7 @@
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
- displayId);
+ displayId, focusGrantToken);
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
@@ -8563,10 +8591,10 @@
}
}
- void grantEmbeddedWindowFocus(Session session, IBinder inputToken, boolean grantFocus) {
+ void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
- mEmbeddedWindowController.get(inputToken);
+ mEmbeddedWindowController.getByFocusToken(focusToken);
if (embeddedWindow == null) {
Slog.e(TAG, "Embedded window not found");
return;
@@ -8575,6 +8603,11 @@
Slog.e(TAG, "Window not in session:" + session);
return;
}
+ IBinder inputToken = embeddedWindow.getInputChannelToken();
+ if (inputToken == null) {
+ Slog.e(TAG, "Focus token found but input channel token not found");
+ return;
+ }
SurfaceControl.Transaction t = mTransactionFactory.get();
final int displayId = embeddedWindow.mDisplayId;
if (grantFocus) {
@@ -8604,7 +8637,7 @@
}
}
- void grantEmbeddedWindowFocus(Session session, IWindow callingWindow, IBinder targetInputToken,
+ void grantEmbeddedWindowFocus(Session session, IWindow callingWindow, IBinder targetFocusToken,
boolean grantFocus) {
synchronized (mGlobalLock) {
final WindowState hostWindow =
@@ -8618,7 +8651,7 @@
return;
}
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
- mEmbeddedWindowController.get(targetInputToken);
+ mEmbeddedWindowController.getByFocusToken(targetFocusToken);
if (embeddedWindow == null) {
Slog.e(TAG, "Embedded window not found");
return;
@@ -8629,7 +8662,7 @@
}
SurfaceControl.Transaction t = mTransactionFactory.get();
if (grantFocus) {
- t.requestFocusTransfer(targetInputToken, embeddedWindow.toString(),
+ t.requestFocusTransfer(embeddedWindow.getInputChannelToken(), embeddedWindow.toString(),
hostWindow.mInputChannel.getToken(),
hostWindow.getName(),
hostWindow.getDisplayId()).apply();
@@ -8638,7 +8671,7 @@
"reason=grantEmbeddedWindowFocus(true)");
} else {
t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(),
- targetInputToken,
+ embeddedWindow.getInputChannelToken(),
embeddedWindow.toString(),
hostWindow.getDisplayId()).apply();
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8864b98..0a02b44 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3848,6 +3848,10 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset state change w=" + this, e);
}
+ final WindowContainer p = getParent();
+ if (p != null) {
+ p.updateOverlayInsetsState(this);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 6d8203c..db231f6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -37,20 +36,15 @@
import android.annotation.CallSuper;
import android.annotation.Nullable;
-import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.window.WindowContext;
@@ -485,9 +479,6 @@
* This should only be called when {@link #mFixedRotationTransformState} is non-null.
*/
private void onFixedRotationStatePrepared() {
- // Send the adjustment info first so when the client receives configuration change, it can
- // get the rotated display metrics.
- notifyFixedRotationTransform(true /* enabled */);
// Resolve the rotated configuration.
onConfigurationChanged(getParent().getConfiguration());
final ActivityRecord r = asActivityRecord();
@@ -543,7 +534,6 @@
for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
final WindowToken token = state.mAssociatedTokens.get(i);
token.mFixedRotationTransformState = null;
- token.notifyFixedRotationTransform(false /* enabled */);
if (applyDisplayRotation == null) {
// Notify cancellation because the display does not change rotation.
token.cancelFixedRotationTransform();
@@ -551,44 +541,6 @@
}
}
- /** Notifies application side to enable or disable the rotation adjustment of display info. */
- void notifyFixedRotationTransform(boolean enabled) {
- FixedRotationAdjustments adjustments = null;
- // A token may contain windows of the same processes or different processes. The list is
- // used to avoid sending the same adjustments to a process multiple times.
- ArrayList<WindowProcessController> notifiedProcesses = null;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- final WindowProcessController app;
- if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
- // Use the host activity because starting window is controlled by window manager.
- final ActivityRecord r = asActivityRecord();
- if (r == null) {
- continue;
- }
- app = r.app;
- } else {
- app = mWmService.mAtmService.mProcessMap.getProcess(w.mSession.mPid);
- }
- if (app == null || !app.hasThread()) {
- continue;
- }
- if (notifiedProcesses == null) {
- notifiedProcesses = new ArrayList<>(2);
- adjustments = enabled ? createFixedRotationAdjustmentsIfNeeded() : null;
- } else if (notifiedProcesses.contains(app)) {
- continue;
- }
- notifiedProcesses.add(app);
- try {
- mWmService.mAtmService.getLifecycleManager().scheduleTransaction(
- app.getThread(), FixedRotationAdjustmentsItem.obtain(token, adjustments));
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to schedule DisplayAdjustmentsItem to " + app, e);
- }
- }
- }
-
/** Restores the changes that applies to this container. */
private void cancelFixedRotationTransform() {
final WindowContainer<?> parent = getParent();
@@ -609,15 +561,6 @@
void onCancelFixedRotationTransform(int originalDisplayRotation) {
}
- FixedRotationAdjustments createFixedRotationAdjustmentsIfNeeded() {
- if (!isFixedRotationTransforming()) {
- return null;
- }
- final DisplayInfo displayInfo = mFixedRotationTransformState.mDisplayInfo;
- return new FixedRotationAdjustments(displayInfo.rotation, displayInfo.appWidth,
- displayInfo.appHeight, displayInfo.displayCutout);
- }
-
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
super.resolveOverrideConfiguration(newParentConfig);
diff --git a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
index 1c574fb..436ac1b 100644
--- a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
@@ -25,7 +25,6 @@
#include <stdio.h>
#include <string.h>
-#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index d29d3fc..9917bcb 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -15,16 +15,14 @@
*/
#define LOG_TAG "UsbHostManagerJNI"
-#include "utils/Log.h"
-
+#include <nativehelper/JNIHelp.h>
#include <stdlib.h>
+#include <usbhost/usbhost.h>
+#include <usbhost/usbhost_jni.h>
#include "jni.h"
-#include <nativehelper/JNIHelp.h>
+#include "utils/Log.h"
-#include <usbhost/usbhost.h>
-
-#define MAX_DESCRIPTORS_LENGTH 4096
static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
// com.android.server.usb.descriptors
@@ -41,26 +39,9 @@
}
int fd = usb_device_get_fd(device);
- if (fd < 0) {
- usb_device_close(device);
- return NULL;
- }
-
- // from android_hardware_UsbDeviceConnection_get_desc()
- jbyte buffer[MAX_DESCRIPTORS_LENGTH];
- lseek(fd, 0, SEEK_SET);
- int numBytes = read(fd, buffer, sizeof(buffer));
- jbyteArray ret = NULL;
+ jbyteArray descriptors = usb_jni_read_descriptors(env, fd);
usb_device_close(device);
-
- if (numBytes > 0) {
- ret = env->NewByteArray(numBytes);
- env->SetByteArrayRegion(ret, 0, numBytes, buffer);
- } else {
- ALOGE("error reading descriptors\n");
- }
-
- return ret;
+ return descriptors;
}
jstring JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getDescriptorString_1native(
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 3ab5920..0a9ce2f 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -25,7 +25,6 @@
#include "MtpDescriptors.h"
#include <stdio.h>
-#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index a629b69..e29d2ca 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -23,7 +23,6 @@
#include "android_runtime/Log.h"
#include <stdio.h>
-#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -31,8 +30,6 @@
#include <usbhost/usbhost.h>
-#define MAX_DESCRIPTORS_LENGTH 4096
-
namespace android
{
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
index cc385c7..a1cba94 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
@@ -35,6 +35,7 @@
import android.security.Credentials;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
+import android.util.PluralsMessageFormatter;
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -46,7 +47,9 @@
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class CertificateMonitor {
protected static final int MONITORING_CERT_NOTIFICATION_ID = SystemMessage.NOTE_SSL_CERT_INFO;
@@ -212,10 +215,15 @@
dialogIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
null, UserHandle.of(parentUserId));
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", pendingCertificateCount);
+
return new Notification.Builder(userContext, SystemNotificationChannels.SECURITY)
.setSmallIcon(smallIconId)
- .setContentTitle(resources.getQuantityText(R.plurals.ssl_ca_cert_warning,
- pendingCertificateCount))
+ .setContentTitle(PluralsMessageFormatter.format(
+ resources,
+ arguments,
+ R.string.ssl_ca_cert_warning))
.setContentText(contentText)
.setContentIntent(notifyIntent)
.setShowWhen(false)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1fe71f8..74e04ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -183,6 +183,7 @@
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
@@ -376,6 +377,8 @@
"com.android.server.searchui.SearchUiManagerService";
private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
"com.android.server.smartspace.SmartspaceManagerService";
+ private static final String CLOUDSEARCH_MANAGER_SERVICE_CLASS =
+ "com.android.server.cloudsearch.CloudSearchManagerService";
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
"com.android.server.DeviceIdleController";
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -1452,6 +1455,10 @@
mSystemServiceManager.startService(KeyChainSystemService.class);
t.traceEnd();
+ t.traceBegin("StartBinaryTransparencyService");
+ mSystemServiceManager.startService(BinaryTransparencyService.class);
+ t.traceEnd();
+
t.traceBegin("StartSchedulingPolicyService");
ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
t.traceEnd();
@@ -1853,6 +1860,12 @@
mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ // CloudSearch manager service
+ // TODO: add deviceHasConfigString(context, R.string.config_defaultCloudSearchServices)
+ t.traceBegin("StartCloudSearchService");
+ mSystemServiceManager.startService(CLOUDSEARCH_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("InitConnectivityModuleConnector");
try {
ConnectivityModuleConnector.getInstance().init(context);
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
index ced24e0..ae4227b 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
@@ -19,8 +19,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.IBinder;
import android.service.selectiontoolbar.ISelectionToolbarRenderService;
import android.service.selectiontoolbar.SelectionToolbarRenderService;
+import android.util.Slog;
import android.view.selectiontoolbar.ISelectionToolbarCallback;
import android.view.selectiontoolbar.ShowInfo;
@@ -35,12 +37,14 @@
AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
private final ComponentName mComponentName;
+ private final IBinder mRemoteCallback;
-
- RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId) {
+ RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId,
+ IBinder callback) {
super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent(
serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface);
mComponentName = serviceName;
+ mRemoteCallback = callback;
// Bind right away.
connect();
}
@@ -50,19 +54,31 @@
return TIMEOUT_IDLE_UNBIND_MS;
}
+ @Override // from ServiceConnector.Impl
+ protected void onServiceConnectionStatusChanged(ISelectionToolbarRenderService service,
+ boolean connected) {
+ try {
+ if (connected) {
+ service.onConnected(mRemoteCallback);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception calling onConnected().", e);
+ }
+ }
+
public ComponentName getComponentName() {
return mComponentName;
}
- public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
- run((s) -> s.onShow(showInfo, callback));
+ public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
+ run((s) -> s.onShow(callingUid, showInfo, callback));
}
public void onHide(long widgetToken) {
run((s) -> s.onHide(widgetToken));
}
- public void onDismiss(long widgetToken) {
- run((s) -> s.onDismiss(widgetToken));
+ public void onDismiss(int callingUid, long widgetToken) {
+ run((s) -> s.onDismiss(callingUid, widgetToken));
}
}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
index 235f547..525a931 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -23,12 +23,17 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.hardware.input.InputManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.service.selectiontoolbar.ISelectionToolbarRenderServiceCallback;
import android.util.Slog;
import android.view.selectiontoolbar.ISelectionToolbarCallback;
import android.view.selectiontoolbar.ShowInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
final class SelectionToolbarManagerServiceImpl extends
@@ -41,9 +46,14 @@
@Nullable
private RemoteSelectionToolbarRenderService mRemoteService;
+ InputManagerInternal mInputManagerInternal;
+ private final SelectionToolbarRenderServiceRemoteCallback mRemoteServiceCallback =
+ new SelectionToolbarRenderServiceRemoteCallback();
+
protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master,
@NonNull Object lock, int userId) {
super(master, lock, userId);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
updateRemoteServiceLocked();
}
@@ -78,7 +88,7 @@
void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) {
final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
- remoteService.onShow(showInfo, callback);
+ remoteService.onShow(Binder.getCallingUid(), showInfo, callback);
}
}
@@ -94,7 +104,7 @@
void dismissToolbar(long widgetToken) {
final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
- remoteService.onDismiss(widgetToken);
+ remoteService.onDismiss(Binder.getCallingUid(), widgetToken);
}
}
@@ -105,7 +115,7 @@
final String serviceName = getComponentNameLocked();
final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent,
- mUserId);
+ mUserId, mRemoteServiceCallback);
}
return mRemoteService;
}
@@ -125,4 +135,17 @@
}
return si;
}
+
+ private void transferTouchFocus(IBinder source, IBinder target) {
+ mInputManagerInternal.transferTouchFocus(source, target);
+ }
+
+ private final class SelectionToolbarRenderServiceRemoteCallback extends
+ ISelectionToolbarRenderServiceCallback.Stub {
+
+ @Override
+ public void transferTouch(IBinder source, IBinder target) {
+ transferTouchFocus(source, target);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 36246e5..9e221be 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -62,12 +62,14 @@
"truth-prebuilt",
// TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
"testng",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.mock",
"android.test.base",
"android.test.runner",
+ "servicestests-core-utils",
],
jni_libs: [
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
index a4de08a..4e0bb36 100644
--- a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<sensor-privacy persistence-version="1" version="1">
<user id="0" enabled="false">
- <individual-sensor-privacy sensor="1" enabled="true" />
- <individual-sensor-privacy sensor="2" enabled="true" />
+ <individual-sensor-privacy sensor="1" enabled="true" last-change="100" />
+ <individual-sensor-privacy sensor="2" enabled="true" last-change="100" />
</user>
</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml
new file mode 100644
index 0000000..2d192db
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="2" version="2">
+ <sensor-state toggle-type="1" user-id="0" sensor="1" state-type="1" last-change="123" />
+ <sensor-state toggle-type="1" user-id="0" sensor="2" state-type="2" last-change="123" />
+</sensor-privacy>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml
new file mode 100644
index 0000000..7bb38b4
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="2" version="2">
+ <sensor-state toggle-type="2" user-id="0" sensor="1" state-type="1" last-change="1234" />
+ <sensor-state toggle-type="2" user-id="0" sensor="2" state-type="2" last-change="1234" />
+</sensor-privacy>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 40b3664..9b04ae4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -344,6 +344,30 @@
callStart(instance);
assertFalse(instance.isForceAllAppsStandbyEnabled());
+
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(false);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false});
+
+ // Toggle the auto restricted bucket feature flag on bg restriction, shouldn't make a
+ // difference.
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(true);
+
areJobsRestricted(instance,
new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
@@ -364,6 +388,9 @@
assertTrue(instance.isForceAllAppsStandbyEnabled());
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(false);
+
areJobsRestricted(instance,
new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
@@ -379,6 +406,29 @@
new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
new boolean[] {true, true, true, false});
+ // Toggle the auto restricted bucket feature flag on bg restriction, shouldn't make a
+ // difference.
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(true);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false},
+ true);
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, false});
+
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(false);
+
// Toggle the foreground state.
assertFalse(instance.isUidActive(UID_1));
@@ -500,9 +550,35 @@
new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
new boolean[] {true, false, false, true, false});
+ // Toggle the auto restricted bucket feature flag on bg restriction.
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(true);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+
// Toggle power saver, should still be the same.
mPowerSaveMode = true;
mPowerSaveObserver.accept(getPowerSaveState());
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(false);
areJobsRestricted(instance,
new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
@@ -524,9 +600,36 @@
new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
new boolean[] {true, false, false, true, false});
+ // Toggle the auto restricted bucket feature flag on bg restriction.
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(true);
+
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false},
+ false);
+ areJobsRestricted(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false},
+ true);
+
+ areAlarmsRestrictedByBatterySaver(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {true, true, true, true, false});
+ areAlarmsRestrictedByFAS(instance,
+ new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
+ new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
+ new boolean[] {false, false, false, false, false});
+
mPowerSaveMode = false;
mPowerSaveObserver.accept(getPowerSaveState());
+ when(mMockIActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled())
+ .thenReturn(false);
+
areJobsRestricted(instance,
new int[] {UID_1, UID_10_1, UID_2, UID_10_2, Process.SYSTEM_UID},
new String[] {PACKAGE_1, PACKAGE_1, PACKAGE_2, PACKAGE_2, PACKAGE_SYSTEM},
diff --git a/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java
new file mode 100644
index 0000000..fac37fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+/**
+ * Test {@link CircularQueue}.
+ */
+public class CircularQueueTest {
+
+ private CircularQueue<Integer, String> mQueue;
+ private static final int LIMIT = 2;
+
+ @Test
+ public void testQueueInsertionAndDeletion() {
+ mQueue = new CircularQueue<>(LIMIT);
+ mQueue.put(1, "A");
+ assertEquals(mQueue.getElement(1), "A");
+ mQueue.removeElement(1);
+ assertNull(mQueue.getElement(1));
+ }
+
+ @Test
+ public void testQueueLimit() {
+ mQueue = new CircularQueue<>(LIMIT);
+ mQueue.put(1, "A");
+ mQueue.put(2, "B");
+ mQueue.put(3, "C");
+ assertNull(mQueue.getElement(1));
+ assertEquals(mQueue.getElement(2), "B");
+ assertEquals(mQueue.getElement(3), "C");
+
+ }
+
+ @Test
+ public void testQueueElements() {
+ mQueue = new CircularQueue<>(LIMIT);
+ mQueue.put(1, "A");
+ mQueue.put(2, "B");
+ assertEquals(mQueue.values().size(), 2);
+ mQueue.put(3, "C");
+ assertEquals(mQueue.values().size(), 2);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
new file mode 100644
index 0000000..28c91aa
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -0,0 +1,2441 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG;
+import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS;
+import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
+import android.app.ActivityManagerInternal.BindServiceEventListener;
+import android.app.ActivityManagerInternal.BroadcastEventListener;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.role.RoleManager;
+import android.app.usage.AppStandbyInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
+import android.os.BatteryManagerInternal;
+import android.os.BatteryStatsInternal;
+import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppStateTracker;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
+import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates;
+import com.android.server.am.AppBatteryExemptionTracker.UidStateEventWithBattery;
+import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
+import com.android.server.am.AppBindServiceEventsTracker.AppBindServiceEventsPolicy;
+import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
+import com.android.server.am.AppFGSTracker.AppFGSPolicy;
+import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.AppRestrictionController.NotificationHelper;
+import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
+import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.usage.AppStandbyInternal;
+import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link AppRestrictionController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:BackgroundRestrictionTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class BackgroundRestrictionTest {
+ private static final String TAG = BackgroundRestrictionTest.class.getSimpleName();
+
+ private static final int TEST_USER0 = UserHandle.USER_SYSTEM;
+ private static final int TEST_USER1 = UserHandle.MIN_SECONDARY_USER_ID;
+ private static final int[] TEST_USERS = new int[] {TEST_USER0, TEST_USER1};
+ private static final String TEST_PACKAGE_BASE = "test_";
+ private static final int TEST_PACKAGE_APPID_BASE = Process.FIRST_APPLICATION_UID;
+ private static final int[] TEST_PACKAGE_USER0_UIDS = new int[] {
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 0),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 1),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 2),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 3),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 4),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 5),
+ UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 6),
+ };
+ private static final int[] TEST_PACKAGE_USER1_UIDS = new int[] {
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 0),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 1),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 2),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 3),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 4),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 5),
+ UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 6),
+ };
+ private static final int[][] TEST_UIDS = new int[][] {
+ TEST_PACKAGE_USER0_UIDS,
+ TEST_PACKAGE_USER1_UIDS,
+ };
+ private static final int[] TEST_STANDBY_BUCKETS = new int[] {
+ STANDBY_BUCKET_EXEMPTED,
+ STANDBY_BUCKET_ACTIVE,
+ STANDBY_BUCKET_WORKING_SET,
+ STANDBY_BUCKET_FREQUENT,
+ STANDBY_BUCKET_RARE,
+ STANDBY_BUCKET_RESTRICTED,
+ STANDBY_BUCKET_NEVER,
+ };
+
+ private static final int BATTERY_FULL_CHARGE_MAH = 5_000;
+
+ @Mock private ActivityManagerInternal mActivityManagerInternal;
+ @Mock private ActivityManagerService mActivityManagerService;
+ @Mock private AppOpsManager mAppOpsManager;
+ @Mock private AppStandbyInternal mAppStandbyInternal;
+ @Mock private AppHibernationManagerInternal mAppHibernationInternal;
+ @Mock private AppStateTracker mAppStateTracker;
+ @Mock private BatteryManagerInternal mBatteryManagerInternal;
+ @Mock private BatteryStatsInternal mBatteryStatsInternal;
+ @Mock private DeviceIdleInternal mDeviceIdleInternal;
+ @Mock private IActivityManager mIActivityManager;
+ @Mock private UserManagerInternal mUserManagerInternal;
+ @Mock private PackageManager mPackageManager;
+ @Mock private PackageManagerInternal mPackageManagerInternal;
+ @Mock private NotificationManager mNotificationManager;
+ @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
+ @Mock private MediaSessionManager mMediaSessionManager;
+ @Mock private RoleManager mRoleManager;
+
+ private long mCurrentTimeMillis;
+
+ @Captor private ArgumentCaptor<AppStateTracker.BackgroundRestrictedAppListener> mFasListenerCap;
+ private AppStateTracker.BackgroundRestrictedAppListener mFasListener;
+
+ @Captor private ArgumentCaptor<AppIdleStateChangeListener> mIdleStateListenerCap;
+ private AppIdleStateChangeListener mIdleStateListener;
+
+ @Captor private ArgumentCaptor<IUidObserver> mUidObserversCap;
+ private IUidObserver mUidObservers;
+
+ @Captor private ArgumentCaptor<OnActiveSessionsChangedListener> mActiveSessionListenerCap;
+ private OnActiveSessionsChangedListener mActiveSessionListener;
+
+ @Captor private ArgumentCaptor<BroadcastEventListener> mBroadcastEventListenerCap;
+ private BroadcastEventListener mBroadcastEventListener;
+
+ @Captor private ArgumentCaptor<BindServiceEventListener> mBindServiceEventListenerCap;
+ private BindServiceEventListener mBindServiceEventListener;
+
+ private Context mContext = getInstrumentation().getTargetContext();
+ private TestBgRestrictionInjector mInjector;
+ private AppRestrictionController mBgRestrictionController;
+ private AppBatteryTracker mAppBatteryTracker;
+ private AppBatteryPolicy mAppBatteryPolicy;
+ private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
+ private AppBroadcastEventsTracker mAppBroadcastEventsTracker;
+ private AppBindServiceEventsTracker mAppBindServiceEventsTracker;
+ private AppFGSTracker mAppFGSTracker;
+ private AppMediaSessionTracker mAppMediaSessionTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ initController();
+ }
+
+ private void initController() throws Exception {
+ mInjector = spy(new TestBgRestrictionInjector(mContext));
+ mBgRestrictionController = spy(new AppRestrictionController(mInjector,
+ mActivityManagerService));
+
+ doReturn(PROCESS_STATE_FOREGROUND_SERVICE).when(mActivityManagerInternal)
+ .getUidProcessState(anyInt());
+ doReturn(TEST_USERS).when(mUserManagerInternal).getUserIds();
+ for (int userId: TEST_USERS) {
+ final ArrayList<AppStandbyInfo> appStandbyInfoList = new ArrayList<>();
+ for (int i = 0; i < TEST_STANDBY_BUCKETS.length; i++) {
+ final String packageName = TEST_PACKAGE_BASE + i;
+ final int uid = UserHandle.getUid(userId, TEST_PACKAGE_APPID_BASE + i);
+ appStandbyInfoList.add(new AppStandbyInfo(packageName, TEST_STANDBY_BUCKETS[i]));
+ doReturn(uid)
+ .when(mPackageManagerInternal)
+ .getPackageUid(packageName, STOCK_PM_FLAGS, userId);
+ doReturn(false)
+ .when(mAppStateTracker)
+ .isAppBackgroundRestricted(uid, packageName);
+ doReturn(TEST_STANDBY_BUCKETS[i])
+ .when(mAppStandbyInternal)
+ .getAppStandbyBucket(eq(packageName), eq(userId), anyLong(), anyBoolean());
+ doReturn(new String[]{packageName})
+ .when(mPackageManager)
+ .getPackagesForUid(eq(uid));
+ doReturn(AppOpsManager.MODE_IGNORED)
+ .when(mAppOpsManager)
+ .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName);
+ doReturn(AppOpsManager.MODE_IGNORED)
+ .when(mAppOpsManager)
+ .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, uid, packageName);
+ doReturn(PERMISSION_DENIED)
+ .when(mPermissionManagerServiceInternal)
+ .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+ doReturn(PERMISSION_DENIED)
+ .when(mPermissionManagerServiceInternal)
+ .checkPermission(packageName, ACCESS_BACKGROUND_LOCATION, userId);
+ }
+ doReturn(appStandbyInfoList).when(mAppStandbyInternal).getAppStandbyBuckets(userId);
+ }
+
+ doReturn(BATTERY_FULL_CHARGE_MAH * 1000).when(mBatteryManagerInternal)
+ .getBatteryFullCharge();
+
+ mBgRestrictionController.onSystemReady();
+
+ verify(mInjector.getAppStateTracker())
+ .addBackgroundRestrictedAppListener(mFasListenerCap.capture());
+ mFasListener = mFasListenerCap.getValue();
+ verify(mInjector.getAppStandbyInternal())
+ .addListener(mIdleStateListenerCap.capture());
+ mIdleStateListener = mIdleStateListenerCap.getValue();
+ verify(mInjector.getIActivityManager())
+ .registerUidObserver(mUidObserversCap.capture(),
+ anyInt(), anyInt(), anyString());
+ mUidObservers = mUidObserversCap.getValue();
+ verify(mAppMediaSessionTracker.mInjector.getMediaSessionManager())
+ .addOnActiveSessionsChangedListener(any(), any(), any(),
+ mActiveSessionListenerCap.capture());
+ mActiveSessionListener = mActiveSessionListenerCap.getValue();
+ verify(mAppBroadcastEventsTracker.mInjector.getActivityManagerInternal())
+ .addBroadcastEventListener(mBroadcastEventListenerCap.capture());
+ mBroadcastEventListener = mBroadcastEventListenerCap.getValue();
+ verify(mAppBindServiceEventsTracker.mInjector.getActivityManagerInternal())
+ .addBindServiceEventListener(mBindServiceEventListenerCap.capture());
+ mBindServiceEventListener = mBindServiceEventListenerCap.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ mBgRestrictionController.getBackgroundHandlerThread().quitSafely();
+ }
+
+ @Test
+ public void testInitialLevels() throws Exception {
+ final int[] expectedLevels = {
+ RESTRICTION_LEVEL_EXEMPTED,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
+ };
+ for (int i = 0; i < TEST_UIDS.length; i++) {
+ final int[] uids = TEST_UIDS[i];
+ for (int j = 0; j < uids.length; j++) {
+ assertEquals(expectedLevels[j],
+ mBgRestrictionController.getRestrictionLevel(uids[j]));
+ assertEquals(expectedLevels[j],
+ mBgRestrictionController.getRestrictionLevel(uids[j],
+ TEST_PACKAGE_BASE + j));
+ }
+ }
+ }
+
+ @Test
+ public void testTogglingBackgroundRestrict() throws Exception {
+ final int testPkgIndex = 2;
+ final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+ final int testUser = TEST_USER0;
+ final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+ final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+ final long timeout = 1_000; // ms
+
+ mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ // Verify the current settings.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+ assertEquals(STANDBY_BUCKET_WORKING_SET, mInjector.getAppStandbyInternal()
+ .getAppStandbyBucket(testPkgName, testUser, SystemClock.elapsedRealtime(), false));
+
+ // Now toggling ON the background restrict.
+ setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+ // We should have been in the background restricted level.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+ // The app should have been put into the restricted standby bucket.
+ verify(mInjector.getAppStandbyInternal(), atLeast(1)).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ eq(REASON_MAIN_FORCED_BY_USER),
+ eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+
+ // Changing to the restricted standby bucket won't make a difference.
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+ STANDBY_BUCKET_RESTRICTED, REASON_MAIN_USAGE);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+ try {
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+
+ clearInvocations(mInjector.getAppStandbyInternal());
+
+ // Toggling back.
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ // It should have gone back to adaptive level.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+ // The app standby bucket should be the rare.
+ verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ eq(REASON_MAIN_FORCED_BY_USER),
+ eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION),
+ eq(REASON_MAIN_USAGE),
+ eq(REASON_SUB_USAGE_USER_INTERACTION));
+
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+
+ clearInvocations(mInjector.getAppStandbyInternal());
+
+ // Now set its UID state active.
+ mUidObservers.onUidActive(testUid);
+
+ // Now toggling ON the background restrict.
+ setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+ // We should have been in the background restricted level.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+ // The app should have NOT been put into the restricted standby bucket.
+ verify(mInjector.getAppStandbyInternal(), never()).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ eq(REASON_MAIN_FORCED_BY_USER),
+ eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+
+ // Now set its UID to idle.
+ mUidObservers.onUidIdle(testUid, false);
+
+ // The app should have been put into the restricted standby bucket because we're idle now.
+ verify(mInjector.getAppStandbyInternal(), timeout(timeout).times(1)).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ eq(REASON_MAIN_FORCED_BY_USER),
+ eq(REASON_SUB_FORCED_USER_FLAG_INTERACTION));
+ }
+
+ @Test
+ public void testTogglingStandbyBucket() throws Exception {
+ final int testPkgIndex = 2;
+ final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+ final int testUser = TEST_USER0;
+ final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+ final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+ final long timeout = 1_000; // ms
+
+ mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ // Verify the current settings.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+ for (int bucket: Arrays.asList(STANDBY_BUCKET_ACTIVE, STANDBY_BUCKET_WORKING_SET,
+ STANDBY_BUCKET_FREQUENT, STANDBY_BUCKET_RARE)) {
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+ bucket, REASON_MAIN_USAGE);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+ try {
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ }
+
+ // Toggling restricted bucket.
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+ STANDBY_BUCKET_RESTRICTED, REASON_MAIN_USAGE);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid);
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+
+ // Toggling exempted bucket.
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ mIdleStateListener.onAppIdleStateChanged(testPkgName, testUser, false,
+ STANDBY_BUCKET_EXEMPTED, REASON_MAIN_FORCED_BY_SYSTEM);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ verifyRestrictionLevel(RESTRICTION_LEVEL_EXEMPTED, testPkgName, testUid);
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_EXEMPTED);
+ }
+
+ @Test
+ public void testBgCurrentDrainMonitor() throws Exception {
+ final BatteryUsageStats stats = mock(BatteryUsageStats.class);
+ final List<BatteryUsageStats> statsList = Arrays.asList(stats);
+ final int testPkgIndex = 2;
+ final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+ final int testUser = TEST_USER0;
+ final int testUid = UserHandle.getUid(testUser,
+ TEST_PACKAGE_APPID_BASE + testPkgIndex);
+ final int testUid2 = UserHandle.getUid(testUser,
+ TEST_PACKAGE_APPID_BASE + testPkgIndex + 1);
+ final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+ final long timeout =
+ AppBatteryTracker.BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG * 2;
+ final long windowMs = 2_000;
+ final float restrictBucketThreshold = 2.0f;
+ final float restrictBucketThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * restrictBucketThreshold / 100.0f;
+ final float bgRestrictedThreshold = 4.0f;
+ final float bgRestrictedThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
+
+ DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
+ DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+ DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
+ DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
+
+ mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ // Verify the current settings.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+ final double[] zeros = new double[]{0.0f, 0.0f};
+ final int[] uids = new int[]{testUid, testUid2};
+
+ try {
+ bgCurrentDrainMonitor = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+ DeviceConfig::getBoolean,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ bgCurrentDrainMonitor.set(true);
+
+ bgCurrentDrainWindow = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+ DeviceConfig::getLong,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ bgCurrentDrainWindow.set(windowMs);
+
+ bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
+
+ bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
+
+ mCurrentTimeMillis = 10_000L;
+ doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
+ doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ });
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // It should have gone to the restricted bucket.
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ verify(mInjector.getAppStandbyInternal()).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ anyInt(), anyInt());
+ });
+
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // We won't change restriction level until user interactions.
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+ eq(testPkgName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(testUser),
+ anyInt(), anyInt());
+ });
+
+ // Trigger user interaction.
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ mIdleStateListener.onUserInteractionStarted(testPkgName, testUser);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ // It should have been back to normal.
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ eq(REASON_MAIN_FORCED_BY_SYSTEM),
+ eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+ eq(REASON_MAIN_USAGE),
+ eq(REASON_SUB_USAGE_USER_INTERACTION));
+ });
+
+ clearInvocations(mInjector.getAppStandbyInternal());
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // It should have gone to the restricted bucket.
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ verify(mInjector.getAppStandbyInternal(), times(1)).restrictApp(
+ eq(testPkgName),
+ eq(testUser),
+ anyInt(), anyInt());
+ });
+
+ clearInvocations(mInjector.getAppStandbyInternal());
+ // Drain a bit more, there shouldn't be any level changes.
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 2, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // We won't change restriction level until user interactions.
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+ eq(testPkgName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(testUser),
+ anyInt(), anyInt());
+ });
+
+ // Sleep a while and set a higher drain
+ Thread.sleep(windowMs);
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{bgRestrictedThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // We won't change restriction level automatically because it needs
+ // user consent.
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+ fail("There shouldn't be level change event like this");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+ eq(testPkgName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(testUser),
+ anyInt(), anyInt());
+ // We should have requested to goto background restricted level.
+ verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
+ eq(testPkgName),
+ eq(testUid));
+ // Verify we have the notification posted.
+ checkNotificationShown(new String[] {testPkgName}, atLeast(1), true);
+ });
+
+ // Turn ON the FAS for real.
+ setBackgroundRestrict(testPkgName, testUid, true, listener);
+
+ // Verify it's background restricted now.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, testPkgName, testUid);
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+
+ // Trigger user interaction.
+ mIdleStateListener.onUserInteractionStarted(testPkgName, testUser);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ fail("There shouldn't be level change event like this");
+ } catch (Exception e) {
+ // Expected.
+ }
+
+ // Turn OFF the FAS.
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ // It'll go back to restricted bucket because it used to behave poorly.
+ listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid);
+ } finally {
+ closeIfNotNull(bgCurrentDrainMonitor);
+ closeIfNotNull(bgCurrentDrainWindow);
+ closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
+ closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
+ }
+ }
+
+ @Test
+ public void testLongFGSMonitor() throws Exception {
+ final int testPkgIndex1 = 1;
+ final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+ final int testUser1 = TEST_USER0;
+ final int testUid1 = UserHandle.getUid(testUser1, TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+ final int testPid1 = 1234;
+
+ final int testPkgIndex2 = 2;
+ final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+ final int testUser2 = TEST_USER0;
+ final int testUid2 = UserHandle.getUid(testUser2, TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+ final int testPid2 = 1235;
+
+ final long windowMs = 2_000;
+ final long thresholdMs = 1_000;
+ final long shortMs = 100;
+
+ DeviceConfigSession<Boolean> longRunningFGSMonitor = null;
+ DeviceConfigSession<Long> longRunningFGSWindow = null;
+ DeviceConfigSession<Long> longRunningFGSThreshold = null;
+
+ try {
+ longRunningFGSMonitor = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_MONITOR_ENABLED,
+ DeviceConfig::getBoolean,
+ AppFGSPolicy.DEFAULT_BG_FGS_MONITOR_ENABLED);
+ longRunningFGSMonitor.set(true);
+
+ longRunningFGSWindow = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_WINDOW,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+ longRunningFGSWindow.set(windowMs);
+
+ longRunningFGSThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+ longRunningFGSThreshold.set(thresholdMs);
+
+ // Basic case
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, true);
+ // Verify we have the notification, it'll include the summary notification though.
+ int notificationId = checkNotificationShown(
+ new String[] {testPkgName1}, timeout(windowMs * 2).times(2), true)[0];
+
+ clearInvocations(mInjector.getNotificationManager());
+ // Sleep a while, verify it won't show another notification.
+ Thread.sleep(windowMs * 2);
+ checkNotificationShown(
+ new String[] {testPkgName1}, timeout(windowMs * 2).times(0), false);
+
+ // Stop this FGS
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, false);
+ checkNotificationGone(testPkgName1, timeout(windowMs), notificationId);
+
+ clearInvocations(mInjector.getNotificationManager());
+ // Start another one and stop it.
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, true);
+ Thread.sleep(shortMs);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, false);
+
+ // Not long enough, it shouldn't show notification in this case.
+ checkNotificationShown(
+ new String[] {testPkgName2}, timeout(windowMs * 2).times(0), false);
+
+ clearInvocations(mInjector.getNotificationManager());
+ // Start the FGS again.
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, true);
+ // Verify we have the notification.
+ notificationId = checkNotificationShown(
+ new String[] {testPkgName2}, timeout(windowMs * 2).times(2), true)[0];
+
+ // Stop this FGS
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, false);
+ checkNotificationGone(testPkgName2, timeout(windowMs), notificationId);
+
+ // Start over with concurrent cases.
+ clearInvocations(mInjector.getNotificationManager());
+ mBgRestrictionController.resetRestrictionSettings();
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, true);
+ Thread.sleep(shortMs);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, true);
+
+ // Verify we've seen both notifications, and test pkg2 should be shown before test pkg1.
+ int[] notificationIds = checkNotificationShown(
+ new String[] {testPkgName2, testPkgName1},
+ timeout(windowMs * 2).times(4), true);
+
+ // Stop both of them.
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, false);
+ checkNotificationGone(testPkgName1, timeout(windowMs), notificationIds[1]);
+ clearInvocations(mInjector.getNotificationManager());
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, false);
+ checkNotificationGone(testPkgName2, timeout(windowMs), notificationIds[0]);
+
+ // Test the interlaced case.
+ clearInvocations(mInjector.getNotificationManager());
+ mBgRestrictionController.resetRestrictionSettings();
+ mAppFGSTracker.reset();
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, true);
+
+ final long initialWaitMs = thresholdMs / 2;
+ Thread.sleep(initialWaitMs);
+
+ for (long remaining = thresholdMs - initialWaitMs; remaining > 0;) {
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, false);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, true);
+ Thread.sleep(shortMs);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, true);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName2, testUid2,
+ testPid2, false);
+ Thread.sleep(shortMs);
+ remaining -= shortMs;
+ }
+
+ // Verify test pkg1 got the notification, but not test pkg2.
+ notificationId = checkNotificationShown(
+ new String[] {testPkgName1}, timeout(windowMs).times(2), true)[0];
+
+ clearInvocations(mInjector.getNotificationManager());
+ // Stop the FGS.
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
+ testPid1, false);
+ checkNotificationGone(testPkgName1, timeout(windowMs), notificationId);
+ } finally {
+ closeIfNotNull(longRunningFGSMonitor);
+ closeIfNotNull(longRunningFGSWindow);
+ closeIfNotNull(longRunningFGSThreshold);
+ }
+ }
+
+ @Test
+ public void testLongFGSExemptions() throws Exception {
+ final int testPkgIndex1 = 1;
+ final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+ final int testUser1 = TEST_USER0;
+ final int testUid1 = UserHandle.getUid(testUser1, TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+ final int testPid1 = 1234;
+
+ final int testPkgIndex2 = 2;
+ final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+ final int testUser2 = TEST_USER0;
+ final int testUid2 = UserHandle.getUid(testUser2, TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+ final int testPid2 = 1235;
+
+ final long windowMs = 2_000;
+ final long thresholdMs = 1_000;
+
+ DeviceConfigSession<Boolean> longRunningFGSMonitor = null;
+ DeviceConfigSession<Long> longRunningFGSWindow = null;
+ DeviceConfigSession<Long> longRunningFGSThreshold = null;
+ DeviceConfigSession<Long> mediaPlaybackFGSThreshold = null;
+ DeviceConfigSession<Long> locationFGSThreshold = null;
+
+ doReturn(testPkgName1).when(mInjector).getPackageName(testPid1);
+ doReturn(testPkgName2).when(mInjector).getPackageName(testPid2);
+
+ try {
+ longRunningFGSMonitor = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_MONITOR_ENABLED,
+ DeviceConfig::getBoolean,
+ AppFGSPolicy.DEFAULT_BG_FGS_MONITOR_ENABLED);
+ longRunningFGSMonitor.set(true);
+
+ longRunningFGSWindow = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_WINDOW,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_WINDOW);
+ longRunningFGSWindow.set(windowMs);
+
+ longRunningFGSThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_LONG_RUNNING_THRESHOLD,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_LONG_RUNNING_THRESHOLD);
+ longRunningFGSThreshold.set(thresholdMs);
+
+ mediaPlaybackFGSThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_MEDIA_PLAYBACK_THRESHOLD,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_MEDIA_PLAYBACK_THRESHOLD);
+ mediaPlaybackFGSThreshold.set(thresholdMs);
+
+ locationFGSThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppFGSPolicy.KEY_BG_FGS_LOCATION_THRESHOLD,
+ DeviceConfig::getLong,
+ AppFGSPolicy.DEFAULT_BG_FGS_LOCATION_THRESHOLD);
+ locationFGSThreshold.set(thresholdMs);
+
+ // Long-running FGS with type "location", but ran for a very short time.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, 0, null, null, null,
+ timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with type "location", and ran for a while.
+ // We shouldn't see notifications in this case.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, thresholdMs * 2, null, null, null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with background location permission.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, 0, ACCESS_BACKGROUND_LOCATION, null, null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with type "mediaPlayback", but ran for a very short time.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, null, null, null,
+ timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with type "mediaPlayback", and ran for a while.
+ // We shouldn't see notifications in this case.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, thresholdMs * 2, null, null, null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with type "camera", and ran for a while.
+ // We shouldn't see notifications in this case.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_CAMERA, thresholdMs * 2, null, null, null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with type "location|mediaPlayback", but ran for a very short time.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ 0, null, null, null, timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with type "location|mediaPlayback", and ran for a while.
+ // We shouldn't see notifications in this case.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ thresholdMs * 2, null, null, null, timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with a media session starts/stops right away.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+ List.of(Pair.create(createMediaControllers(
+ new String[] {testPkgName1}, new int[] {testUid1}), 0L)), null,
+ timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with media session, and ran for a while.
+ // We shouldn't see notifications in this case.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, thresholdMs * 2, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), thresholdMs * 2)), null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with 2 media sessions start/stop right away
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+ List.of(Pair.create(createMediaControllers(
+ new String[] {testPkgName1, testPkgName2},
+ new int[] {testUid1, testUid2}), 0L)), null,
+ timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with 2 media sessions start/stop interlaced.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null,
+ List.of(Pair.create(createMediaControllers(
+ new String[] {testPkgName1, testPkgName2},
+ new int[] {testUid1, testUid2}), thresholdMs),
+ Pair.create(createMediaControllers(
+ new String[] {testPkgName1},
+ new int[] {testUid1}), thresholdMs / 10),
+ Pair.create(createMediaControllers(
+ new String[] {testPkgName2},
+ new int[] {testUid2}), thresholdMs / 10),
+ Pair.create(createMediaControllers(
+ new String[] {testPkgName1},
+ new int[] {testUid1}), thresholdMs / 10)
+ ), null,
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with top state for a very short time.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L),
+ timeout(windowMs * 2).times(2));
+
+ // Long-running FGS with top state for extended time.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null, null, List.of(0L, windowMs * 2, 0L),
+ timeout(windowMs * 2).times(0));
+
+ // Long-running FGS with top state, on and off frequently.
+ runTestLongFGSExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, null, null,
+ List.of(0L, thresholdMs / 10, thresholdMs / 10, thresholdMs / 10,
+ thresholdMs / 10, thresholdMs / 10, thresholdMs / 10),
+ timeout(windowMs * 2).times(2));
+ } finally {
+ closeIfNotNull(longRunningFGSMonitor);
+ closeIfNotNull(longRunningFGSWindow);
+ closeIfNotNull(longRunningFGSThreshold);
+ closeIfNotNull(mediaPlaybackFGSThreshold);
+ closeIfNotNull(locationFGSThreshold);
+ }
+ }
+
+ private void resetBgRestrictionController() {
+ mBgRestrictionController.resetRestrictionSettings();
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ }
+
+ private void runTestLongFGSExemptionOnce(String packageName, int uid, int pid,
+ int serviceType, long sleepMs, String perm,
+ List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges,
+ VerificationMode mode) throws Exception {
+ runExemptionTestOnce(
+ packageName, uid, pid, serviceType, sleepMs, true, perm, mediaControllers,
+ topStateChanges, true, true,
+ () -> checkNotificationShown(new String[] {packageName}, mode, false)
+ );
+ }
+
+ private void runExemptionTestOnce(String packageName, int uid, int pid,
+ int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
+ List<Pair<List<MediaController>, Long>> mediaControllers,
+ List<Long> topStateChanges, boolean resetFGSTracker, boolean resetController,
+ RunnableWithException r) throws Exception {
+ if (resetFGSTracker) {
+ mAppFGSTracker.reset();
+ mAppMediaSessionTracker.reset();
+ }
+ if (resetController) {
+ resetBgRestrictionController();
+ }
+ clearInvocations(mInjector.getNotificationManager());
+
+ Thread topStateThread = null;
+ if (topStateChanges != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ topStateThread = new Thread(() -> {
+ try {
+ latch.await();
+ boolean top = false;
+ for (long l: topStateChanges) {
+ mUidObservers.onUidStateChanged(uid,
+ top ? PROCESS_STATE_TOP : PROCESS_STATE_FOREGROUND_SERVICE,
+ 0, 0);
+ top = !top;
+ Thread.sleep(l);
+ }
+ mUidObservers.onUidGone(uid, false);
+ } catch (InterruptedException | RemoteException e) {
+ }
+ });
+ topStateThread.start();
+ latch.countDown();
+ }
+
+ mAppFGSTracker.onForegroundServiceStateChanged(packageName, uid, pid, true);
+ if (serviceType != FOREGROUND_SERVICE_TYPE_NONE) {
+ mAppFGSTracker.mProcessObserver.onForegroundServicesChanged(pid, uid, serviceType);
+ Thread.sleep(sleepMs);
+ if (stopAfterSleep) {
+ // Stop it now.
+ mAppFGSTracker.mProcessObserver.onForegroundServicesChanged(pid, uid,
+ FOREGROUND_SERVICE_TYPE_NONE);
+ }
+ }
+
+ if (perm != null) {
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManagerServiceInternal)
+ .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManagerServiceInternal)
+ .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+ }
+
+ if (mediaControllers != null) {
+ for (Pair<List<MediaController>, Long> entry: mediaControllers) {
+ mActiveSessionListener.onActiveSessionsChanged(entry.first);
+ Thread.sleep(entry.second);
+ }
+ if (stopAfterSleep) {
+ // Stop it now.
+ mActiveSessionListener.onActiveSessionsChanged(null);
+ }
+ }
+
+ r.run();
+
+ // Stop this FGS
+ mAppFGSTracker.onForegroundServiceStateChanged(packageName, uid, pid, false);
+
+ if (perm != null) {
+ doReturn(PERMISSION_DENIED)
+ .when(mPermissionManagerServiceInternal)
+ .checkPermission(packageName, perm, UserHandle.getUserId(uid));
+ doReturn(PERMISSION_DENIED)
+ .when(mPermissionManagerServiceInternal)
+ .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION);
+ }
+ if (topStateThread != null) {
+ topStateThread.join();
+ }
+ }
+
+ private List<MediaController> createMediaControllers(String[] packageNames, int[] uids) {
+ final ArrayList<MediaController> controllers = new ArrayList<>();
+ for (int i = 0; i < packageNames.length; i++) {
+ controllers.add(createMediaController(packageNames[i], uids[i]));
+ }
+ return controllers;
+ }
+
+ private MediaController createMediaController(String packageName, int uid) {
+ final MediaController controller = mock(MediaController.class);
+ final MediaSession.Token token = mock(MediaSession.Token.class);
+ doReturn(packageName).when(controller).getPackageName();
+ doReturn(token).when(controller).getSessionToken();
+ doReturn(uid).when(token).getUid();
+ return controller;
+ }
+
+ @Test
+ public void testBgCurrentDrainMonitorExemptions() throws Exception {
+ final BatteryUsageStats stats = mock(BatteryUsageStats.class);
+ final List<BatteryUsageStats> statsList = Arrays.asList(stats);
+ final int testPkgIndex1 = 1;
+ final String testPkgName1 = TEST_PACKAGE_BASE + testPkgIndex1;
+ final int testUser = TEST_USER0;
+ final int testUid1 = UserHandle.getUid(testUser,
+ TEST_PACKAGE_APPID_BASE + testPkgIndex1);
+ final int testPid1 = 1234;
+ final int testPkgIndex2 = 2;
+ final String testPkgName2 = TEST_PACKAGE_BASE + testPkgIndex2;
+ final int testUid2 = UserHandle.getUid(testUser,
+ TEST_PACKAGE_APPID_BASE + testPkgIndex2);
+ final int testPid2 = 1235;
+ final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+ final long timeout =
+ AppBatteryTracker.BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG * 2;
+ final long windowMs = 2_000;
+ final float restrictBucketThreshold = 2.0f;
+ final float restrictBucketThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * restrictBucketThreshold / 100.0f;
+ final float bgRestrictedThreshold = 4.0f;
+ final float bgRestrictedThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
+ final float restrictBucketHighThreshold = 25.0f;
+ final float restrictBucketHighThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * restrictBucketHighThreshold / 100.0f;
+ final float bgRestrictedHighThreshold = 25.0f;
+ final float bgRestrictedHighThresholdMah =
+ BATTERY_FULL_CHARGE_MAH * bgRestrictedHighThreshold / 100.0f;
+ final long bgMediaPlaybackMinDuration = 1_000L;
+ final long bgLocationMinDuration = 1_000L;
+
+ DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
+ DeviceConfigSession<Long> bgCurrentDrainWindow = null;
+ DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
+ DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
+ DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketHighThreshold = null;
+ DeviceConfigSession<Float> bgCurrentDrainBgRestrictedHighThreshold = null;
+ DeviceConfigSession<Long> bgMediaPlaybackMinDurationThreshold = null;
+ DeviceConfigSession<Long> bgLocationMinDurationThreshold = null;
+ DeviceConfigSession<Boolean> bgCurrentDrainEventDurationBasedThresholdEnabled = null;
+ DeviceConfigSession<Boolean> bgBatteryExemptionEnabled = null;
+
+ mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+
+ setBackgroundRestrict(testPkgName1, testUid1, false, listener);
+
+ // Verify the current settings.
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName1, testUid1);
+
+ final double[] zeros = new double[]{0.0f, 0.0f};
+ final int[] uids = new int[]{testUid1, testUid2};
+
+ doReturn(testPkgName1).when(mInjector).getPackageName(testPid1);
+ doReturn(testPkgName2).when(mInjector).getPackageName(testPid2);
+
+ try {
+ bgCurrentDrainMonitor = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MONITOR_ENABLED,
+ DeviceConfig::getBoolean,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MONITOR_ENABLED);
+ bgCurrentDrainMonitor.set(true);
+
+ bgCurrentDrainWindow = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_WINDOW,
+ DeviceConfig::getLong,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS);
+ bgCurrentDrainWindow.set(windowMs);
+
+ bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ bgCurrentDrainRestrictedBucketThreshold.set(restrictBucketThreshold);
+
+ bgCurrentDrainBgRestrictedThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_THRESHOLD);
+ bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
+
+ bgCurrentDrainRestrictedBucketHighThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_RESTRICTED_BUCKET_HIGH_THRESHOLD);
+ bgCurrentDrainRestrictedBucketHighThreshold.set(restrictBucketHighThreshold);
+
+ bgCurrentDrainBgRestrictedHighThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED,
+ DeviceConfig::getFloat,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_BG_RESTRICTED_HIGH_THRESHOLD);
+ bgCurrentDrainBgRestrictedHighThreshold.set(bgRestrictedHighThreshold);
+
+ bgMediaPlaybackMinDurationThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION,
+ DeviceConfig::getLong,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION);
+ bgMediaPlaybackMinDurationThreshold.set(bgMediaPlaybackMinDuration);
+
+ bgLocationMinDurationThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION,
+ DeviceConfig::getLong,
+ AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_LOCATION_MIN_DURATION);
+ bgLocationMinDurationThreshold.set(bgLocationMinDuration);
+
+ bgCurrentDrainEventDurationBasedThresholdEnabled = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED,
+ DeviceConfig::getBoolean,
+ AppBatteryPolicy
+ .DEFAULT_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED);
+ bgCurrentDrainEventDurationBasedThresholdEnabled.set(true);
+
+ bgBatteryExemptionEnabled = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ AppBatteryExemptionPolicy.KEY_BG_BATTERY_EXEMPTION_ENABLED,
+ DeviceConfig::getBoolean,
+ AppBatteryExemptionPolicy.DEFAULT_BG_BATTERY_EXEMPTION_ENABLED);
+ bgBatteryExemptionEnabled.set(false);
+
+ mCurrentTimeMillis = 10_000L;
+ doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
+ doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+
+ // Run with a media playback service which starts/stops immediately, we should
+ // goto the restricted bucket.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with a media playback service with extended time. We should be back to normal.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
+ () -> {
+ // A user interaction will bring it back to normal.
+ mIdleStateListener.onUserInteractionStarted(testPkgName1,
+ UserHandle.getUserId(testUid1));
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ // It should have been back to normal.
+ listener.verify(timeout, testUid1, testPkgName1,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ verify(mInjector.getAppStandbyInternal(), times(1)).maybeUnrestrictApp(
+ eq(testPkgName1),
+ eq(UserHandle.getUserId(testUid1)),
+ eq(REASON_MAIN_FORCED_BY_SYSTEM),
+ eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+ eq(REASON_MAIN_USAGE),
+ eq(REASON_SUB_USAGE_USER_INTERACTION));
+ }, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a media playback service with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with a media playback service with extended time, with even higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a media session with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+ null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with a media session with extended time, with even higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+ null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a media session with extended time, with moderate current drain,
+ // but it ran on the top when the location service is active.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+ List.of(0L, timeout * 2), listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a location service with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with a location service with extended time, with even higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a location service with extended time, with moderate current drain,
+ // but it ran on the top when the location service is active.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+ null, null, List.of(0L, timeout * 2), listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with bg location permission, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, false,
+ ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with bg location permission, with even higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, 0, false,
+ ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, null, null, null);
+
+ // Now turn off the event duration based feature flag.
+ bgCurrentDrainEventDurationBasedThresholdEnabled.set(false);
+ // Turn on the battery exemption feature flag.
+ bgBatteryExemptionEnabled.set(true);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+
+ // Run with a media playback service which starts/stops immediately, we should
+ // goto the restricted bucket.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, null, null, null);
+
+ // Run with a media playback service with extended time. We should be back to normal.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
+ () -> {
+ // A user interaction will bring it back to normal.
+ mIdleStateListener.onUserInteractionStarted(testPkgName1,
+ UserHandle.getUserId(testUid1));
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ // It should have been back to normal.
+ listener.verify(timeout, testUid1, testPkgName1,
+ RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
+ verify(mInjector.getAppStandbyInternal(), times(1)).maybeUnrestrictApp(
+ eq(testPkgName1),
+ eq(UserHandle.getUserId(testUid1)),
+ eq(REASON_MAIN_FORCED_BY_SYSTEM),
+ eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
+ eq(REASON_MAIN_USAGE),
+ eq(REASON_SUB_USAGE_USER_INTERACTION));
+ }, windowMs, null, null, null);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ final double[] initialBg = {1, 1}, initialFgs = {1, 1}, initialFg = zeros;
+
+ // Run with a media playback service with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, initialBg, initialFgs, initialFg);
+
+ // Run with a media playback service with extended time, with even higher current drain,
+ // it still should stay in the current restriction level as we exempt the media
+ // playback.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 100, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, initialBg, initialFgs, initialFg);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a media session with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+ null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, initialBg, initialFgs, initialFg);
+
+ // Run with a media session with extended time, with even higher current drain.
+ // it still should stay in the current restriction level as we exempt the media
+ // session.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
+ List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
+ new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
+ null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 100, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, initialBg, initialFgs, initialFg);
+
+ // Start over.
+ resetBgRestrictionController();
+ setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
+ mAppBatteryPolicy.reset();
+
+ // Run with a location service with extended time, with higher current drain.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah - 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
+ null, windowMs, initialBg, initialFgs, initialFg);
+
+ // Run with a location service with extended time, with even higher current drain.
+ // it still should stay in the current restriction level as we exempt the location.
+ runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
+ FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
+ null, null, null, listener, stats, uids,
+ new double[]{restrictBucketHighThresholdMah + 100, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
+ null, windowMs, initialBg, initialFgs, initialFg);
+ } finally {
+ closeIfNotNull(bgCurrentDrainMonitor);
+ closeIfNotNull(bgCurrentDrainWindow);
+ closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
+ closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
+ closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold);
+ closeIfNotNull(bgCurrentDrainBgRestrictedHighThreshold);
+ closeIfNotNull(bgMediaPlaybackMinDurationThreshold);
+ closeIfNotNull(bgLocationMinDurationThreshold);
+ closeIfNotNull(bgCurrentDrainEventDurationBasedThresholdEnabled);
+ closeIfNotNull(bgBatteryExemptionEnabled);
+ }
+ }
+
+ private void runTestBgCurrentDrainExemptionOnce(String packageName, int uid, int pid,
+ int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
+ List<Pair<List<MediaController>, Long>> mediaControllers,
+ List<Long> topStateChanges, TestAppRestrictionLevelListener listener,
+ BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+ boolean expectingTimeout, int expectingLevel, long timeout, boolean resetFGSTracker,
+ RunnableWithException extraVerifiers, long windowMs,
+ double[] initialBg, double[] initialFgs, double[] initialFg) throws Exception {
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ if (initialBg != null) {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ setUidBatteryConsumptions(stats, uids, initialBg, initialFgs, initialFg);
+ mAppBatteryExemptionTracker.reset();
+ mAppBatteryPolicy.reset();
+ }
+ runExemptionTestOnce(
+ packageName, uid, pid, serviceType, sleepMs, stopAfterSleep,
+ perm, mediaControllers, topStateChanges, resetFGSTracker, false,
+ () -> {
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids, bg, fgs, fg, false,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ if (expectingTimeout) {
+ try {
+ listener.verify(timeout, uid, packageName, expectingLevel);
+ fail("There shouldn't be any level change events");
+ } catch (Exception e) {
+ // Expected.
+ }
+ } else {
+ listener.verify(timeout, uid, packageName, expectingLevel);
+ }
+ if (expectingLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+ verify(mInjector.getAppStandbyInternal(),
+ expectingTimeout ? never() : atLeast(1)).restrictApp(
+ eq(packageName),
+ eq(UserHandle.getUserId(uid)),
+ anyInt(), anyInt());
+ } else if (expectingLevel
+ == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
+ verify(mBgRestrictionController,
+ expectingTimeout ? never() : atLeast(1))
+ .handleRequestBgRestricted(eq(packageName), eq(uid));
+ } else {
+ verify(mInjector.getAppStandbyInternal(),
+ expectingTimeout ? never() : atLeast(1))
+ .setAppStandbyBucket(
+ eq(packageName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(UserHandle.getUserId(uid)),
+ anyInt(), anyInt());
+ }
+ if (extraVerifiers != null) {
+ extraVerifiers.run();
+ }
+ }
+ );
+ }
+ );
+ }
+
+ @Test
+ public void testExcessiveBroadcasts() throws Exception {
+ final long windowMs = 5_000;
+ final int threshold = 10;
+ runTestExcessiveEvent(AppBroadcastEventsPolicy.KEY_BG_BROADCAST_MONITOR_ENABLED,
+ AppBroadcastEventsPolicy.DEFAULT_BG_BROADCAST_MONITOR_ENABLED,
+ AppBroadcastEventsPolicy.KEY_BG_BROADCAST_WINDOW,
+ AppBroadcastEventsPolicy.DEFAULT_BG_BROADCAST_WINDOW,
+ AppBroadcastEventsPolicy.KEY_BG_EX_BROADCAST_THRESHOLD,
+ AppBroadcastEventsPolicy.DEFAULT_BG_EX_BROADCAST_THRESHOLD,
+ windowMs, threshold, mBroadcastEventListener::onSendingBroadcast,
+ mAppBroadcastEventsTracker,
+ new long[][] {
+ new long[] {1_000L, 2_000L, 2_000L},
+ new long[] {2_000L, 2_000L, 1_000L},
+ },
+ new int[][] {
+ new int[] {3, 3, 3},
+ new int[] {3, 3, 4},
+ },
+ new boolean[] {
+ true,
+ false,
+ }
+ );
+ }
+
+ @Test
+ public void testExcessiveBindServices() throws Exception {
+ final long windowMs = 5_000;
+ final int threshold = 10;
+ runTestExcessiveEvent(AppBindServiceEventsPolicy.KEY_BG_BIND_SVC_MONITOR_ENABLED,
+ AppBindServiceEventsPolicy.DEFAULT_BG_BIND_SVC_MONITOR_ENABLED,
+ AppBindServiceEventsPolicy.KEY_BG_BIND_SVC_WINDOW,
+ AppBindServiceEventsPolicy.DEFAULT_BG_BIND_SVC_WINDOW,
+ AppBindServiceEventsPolicy.KEY_BG_EX_BIND_SVC_THRESHOLD,
+ AppBindServiceEventsPolicy.DEFAULT_BG_EX_BIND_SVC_THRESHOLD,
+ windowMs, threshold, mBindServiceEventListener::onBindingService,
+ mAppBindServiceEventsTracker,
+ new long[][] {
+ new long[] {0L, 2_000L, 4_000L, 1_000L},
+ new long[] {2_000L, 2_000L, 2_000L, 2_000L},
+ },
+ new int[][] {
+ new int[] {8, 3, 1, 0}, // Will goto restricted bucket.
+ new int[] {3, 3, 3, 3},
+ },
+ new boolean[] {
+ false,
+ true,
+ }
+ );
+ }
+
+ private void runTestExcessiveEvent(String keyEnable, boolean defaultEnable,
+ String keyWindow, long defaultWindow, String keyThreshold, int defaultThreshold,
+ long windowMs, int threshold, BiConsumer<String, Integer> eventEmitter,
+ BaseAppStateEventsTracker tracker, long[][] waitMs, int[][] events,
+ boolean[] expectingTimeout) throws Exception {
+ final int testPkgIndex = 1;
+ final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
+ final int testUser = TEST_USER0;
+ final int testUid = UserHandle.getUid(testUser, TEST_PACKAGE_APPID_BASE + testPkgIndex);
+ final int testPid = 1234;
+
+ final long timeoutMs = 2_000;
+
+ final TestAppRestrictionLevelListener listener = new TestAppRestrictionLevelListener();
+
+ mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
+ setBackgroundRestrict(testPkgName, testUid, false, listener);
+
+ DeviceConfigSession<Boolean> enableMonitor = null;
+ DeviceConfigSession<Long> eventsWindow = null;
+ DeviceConfigSession<Integer> eventsThreshold = null;
+
+ doReturn(testPkgName).when(mInjector).getPackageName(testPid);
+
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+
+ try {
+ enableMonitor = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ keyEnable,
+ DeviceConfig::getBoolean,
+ defaultEnable);
+ enableMonitor.set(true);
+
+ eventsWindow = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ keyWindow,
+ DeviceConfig::getLong,
+ defaultWindow);
+ eventsWindow.set(windowMs);
+
+ eventsThreshold = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ keyThreshold,
+ DeviceConfig::getInt,
+ defaultThreshold);
+ eventsThreshold.set(threshold);
+
+ for (int i = 0; i < waitMs.length; i++) {
+ resetBgRestrictionController();
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ tracker.reset();
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+ for (int j = 0; j < waitMs[i].length; j++) {
+ for (int k = 0; k < events[i][j]; k++) {
+ eventEmitter.accept(testPkgName, testUid);
+ }
+ Thread.sleep(waitMs[i][j]);
+ }
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ if (expectingTimeout[i]) {
+ verifyRestrictionLevel(RESTRICTION_LEVEL_ADAPTIVE_BUCKET, testPkgName, testUid);
+ try {
+ listener.verify(timeoutMs, testUid, testPkgName,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ fail("There shouldn't be any level change events");
+ } catch (TimeoutException e) {
+ // expected.
+ }
+ } else {
+ verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ testPkgName, testUid);
+ listener.verify(timeoutMs, testUid, testPkgName,
+ RESTRICTION_LEVEL_RESTRICTED_BUCKET);
+ }
+ }
+ } finally {
+ closeIfNotNull(enableMonitor);
+ closeIfNotNull(eventsWindow);
+ closeIfNotNull(eventsThreshold);
+ }
+ }
+
+ private int[] checkNotificationShown(String[] packageName, VerificationMode mode,
+ boolean verifyNotification) throws Exception {
+ final ArgumentCaptor<Integer> notificationIdCaptor =
+ ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Notification> notificationCaptor =
+ ArgumentCaptor.forClass(Notification.class);
+ verify(mInjector.getNotificationManager(), mode).notifyAsUser(any(),
+ notificationIdCaptor.capture(), notificationCaptor.capture(), any());
+ final int[] notificationId = new int[packageName.length];
+ if (verifyNotification) {
+ for (int i = 0, j = 0; i < packageName.length; j++) {
+ final int id = notificationIdCaptor.getAllValues().get(j);
+ if (id == NotificationHelper.SUMMARY_NOTIFICATION_ID) {
+ continue;
+ }
+ final Notification n = notificationCaptor.getAllValues().get(j);
+ notificationId[i] = id;
+ assertTrue(NotificationHelper.SUMMARY_NOTIFICATION_ID < notificationId[i]);
+ assertEquals(NotificationHelper.GROUP_KEY, n.getGroup());
+ assertEquals(ABUSIVE_BACKGROUND_APPS, n.getChannelId());
+ assertEquals(packageName[i], n.extras.getString(Intent.EXTRA_PACKAGE_NAME));
+ i++;
+ }
+ }
+ return notificationId;
+ }
+
+ private void checkNotificationGone(String packageName, VerificationMode mode,
+ int notificationId) throws Exception {
+ final ArgumentCaptor<Integer> notificationIdCaptor =
+ ArgumentCaptor.forClass(Integer.class);
+ verify(mInjector.getNotificationManager(), mode).cancel(notificationIdCaptor.capture());
+ assertEquals(notificationId, notificationIdCaptor.getValue().intValue());
+ }
+
+ private void closeIfNotNull(DeviceConfigSession<?> config) throws Exception {
+ if (config != null) {
+ config.close();
+ }
+ }
+
+ private interface RunnableWithException {
+ void run() throws Exception;
+ }
+
+ private void runTestBgCurrentDrainMonitorOnce(TestAppRestrictionLevelListener listener,
+ BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+ RunnableWithException runnable) throws Exception {
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids, bg, fgs, fg, true, runnable);
+ }
+
+ private void runTestBgCurrentDrainMonitorOnce(TestAppRestrictionLevelListener listener,
+ BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
+ boolean resetListener, RunnableWithException runnable) throws Exception {
+ if (resetListener) {
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ }
+ setUidBatteryConsumptions(stats, uids, bg, fgs, fg);
+ runnable.run();
+ }
+
+ private void setUidBatteryConsumptions(BatteryUsageStats stats, int[] uids, double[] bg,
+ double[] fgs, double[] fg) {
+ ArrayList<UidBatteryConsumer> consumers = new ArrayList<>();
+ for (int i = 0; i < uids.length; i++) {
+ consumers.add(mockUidBatteryConsumer(uids[i], bg[i], fgs[i], fg[i]));
+ }
+ doReturn(consumers).when(stats).getUidBatteryConsumers();
+ }
+
+ private UidBatteryConsumer mockUidBatteryConsumer(int uid, double bg, double fgs, double fg) {
+ UidBatteryConsumer uidConsumer = mock(UidBatteryConsumer.class);
+ doReturn(uid).when(uidConsumer).getUid();
+ doReturn(bg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_BG));
+ doReturn(fgs).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FGS));
+ doReturn(fg).when(uidConsumer).getConsumedPower(eq(BATT_DIMEN_FG));
+ return uidConsumer;
+ }
+
+ private void setBackgroundRestrict(String pkgName, int uid, boolean restricted,
+ TestAppRestrictionLevelListener listener) throws Exception {
+ Log.i(TAG, "Setting background restrict to " + restricted + " for " + pkgName + " " + uid);
+ listener.mLatchHolder[0] = new CountDownLatch(1);
+ doReturn(restricted).when(mAppStateTracker).isAppBackgroundRestricted(uid, pkgName);
+ mFasListener.updateBackgroundRestrictedForUidPackage(uid, pkgName, restricted);
+ waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
+ }
+
+ private class TestAppRestrictionLevelListener implements AppBackgroundRestrictionListener {
+ private final CountDownLatch[] mLatchHolder = new CountDownLatch[1];
+ final int[] mUidHolder = new int[1];
+ final String[] mPkgNameHolder = new String[1];
+ final int[] mLevelHolder = new int[1];
+
+ @Override
+ public void onRestrictionLevelChanged(int uid, String packageName, int newLevel) {
+ mUidHolder[0] = uid;
+ mPkgNameHolder[0] = packageName;
+ mLevelHolder[0] = newLevel;
+ mLatchHolder[0].countDown();
+ };
+
+ void verify(long timeout, int uid, String pkgName, int level) throws Exception {
+ if (!mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException();
+ }
+ assertEquals(uid, mUidHolder[0]);
+ assertEquals(pkgName, mPkgNameHolder[0]);
+ assertEquals(level, mLevelHolder[0]);
+ }
+ }
+
+ private void verifyRestrictionLevel(int level, String pkgName, int uid) {
+ assertEquals(level, mBgRestrictionController.getRestrictionLevel(uid));
+ assertEquals(level, mBgRestrictionController.getRestrictionLevel(uid, pkgName));
+ }
+
+ private void waitForIdleHandler(Handler handler) {
+ waitForIdleHandler(handler, Duration.ofSeconds(1));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
+ }
+ }
+
+ @Test
+ public void testMergeAppStateDurations() throws Exception {
+ final BaseAppStateDurations testObj = new BaseAppStateDurations(0, "", 1, "", null) {};
+ assertAppStateDurations(null, testObj.add(null, null));
+ assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.add(
+ null, new LinkedList<BaseTimeEvent>()));
+ assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.add(
+ new LinkedList<BaseTimeEvent>(), null));
+ assertAppStateDurations(createDurations(1), testObj.add(
+ createDurations(1), new LinkedList<BaseTimeEvent>()));
+ assertAppStateDurations(createDurations(1), testObj.add(
+ new LinkedList<BaseTimeEvent>(), createDurations(1)));
+ assertAppStateDurations(createDurations(1, 4, 5, 8, 9), testObj.add(
+ createDurations(1, 3, 5, 7, 9), createDurations(2, 4, 6, 8, 10)));
+ assertAppStateDurations(createDurations(1, 5), testObj.add(
+ createDurations(1, 2, 3, 4), createDurations(2, 3, 4, 5)));
+ assertAppStateDurations(createDurations(1, 4, 6, 9), testObj.add(
+ createDurations(2, 4, 6, 9), createDurations(1, 4, 7, 8)));
+ assertAppStateDurations(createDurations(1, 4, 5, 8, 9, 10), testObj.add(
+ createDurations(1, 4, 6, 8), createDurations(1, 3, 5, 8, 9, 10)));
+ }
+
+ @Test
+ public void testSubtractAppStateDurations() throws Exception {
+ final BaseAppStateDurations testObj = new BaseAppStateDurations(0, "", 1, "", null) {};
+ assertAppStateDurations(null, testObj.subtract(null, null));
+ assertAppStateDurations(null, testObj.subtract(null, new LinkedList<BaseTimeEvent>()));
+ assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+ new LinkedList<BaseTimeEvent>(), null));
+ assertAppStateDurations(createDurations(1), testObj.subtract(
+ createDurations(1), new LinkedList<BaseTimeEvent>()));
+ assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+ new LinkedList<BaseTimeEvent>(), createDurations(1)));
+ assertAppStateDurations(new LinkedList<BaseTimeEvent>(), testObj.subtract(
+ createDurations(1), createDurations(1)));
+ assertAppStateDurations(createDurations(1, 2, 5, 6, 9, 10), testObj.subtract(
+ createDurations(1, 3, 5, 7, 9), createDurations(2, 4, 6, 8, 10)));
+ assertAppStateDurations(createDurations(1, 2, 3, 4), testObj.subtract(
+ createDurations(1, 4, 6, 7, 9, 10), createDurations(2, 3, 5, 8, 9, 10)));
+ assertAppStateDurations(createDurations(3, 4, 9, 10), testObj.subtract(
+ createDurations(1, 4, 6, 8, 9, 10), createDurations(1, 3, 5, 8)));
+ assertAppStateDurations(createDurations(1, 2, 3, 4, 5, 6, 7, 8), testObj.subtract(
+ createDurations(1, 6, 7, 8), createDurations(2, 3, 4, 5, 8, 10)));
+ assertAppStateDurations(createDurations(5, 6), testObj.subtract(
+ createDurations(2, 3, 5, 6), createDurations(1, 4, 7, 8)));
+ assertAppStateDurations(createDurations(2, 3, 4, 5, 6, 7, 8), testObj.subtract(
+ createDurations(1), createDurations(1, 2, 3, 4, 5, 6, 7, 8)));
+ }
+
+ private void assertAppStateDurations(LinkedList<BaseTimeEvent> expected,
+ LinkedList<BaseTimeEvent> actual) throws Exception {
+ assertListEquals(expected, actual);
+ }
+
+ private <T> void assertListEquals(LinkedList<T> expected, LinkedList<T> actual) {
+ assertEquals(expected == null || expected.isEmpty(), actual == null || actual.isEmpty());
+ if (expected != null) {
+ if (expected.size() > 0) {
+ assertEquals(expected.size(), actual.size());
+ }
+ while (expected.peek() != null) {
+ assertTrue(expected.poll().equals(actual.poll()));
+ }
+ }
+ }
+
+ private LinkedList<BaseTimeEvent> createDurations(long... timestamps) {
+ return Arrays.stream(timestamps).mapToObj(BaseTimeEvent::new)
+ .collect(LinkedList<BaseTimeEvent>::new, LinkedList<BaseTimeEvent>::add,
+ (a, b) -> a.addAll(b));
+ }
+
+ private LinkedList<Integer> createIntLinkedList(int[] vals) {
+ return Arrays.stream(vals).collect(LinkedList<Integer>::new, LinkedList<Integer>::add,
+ (a, b) -> a.addAll(b));
+ }
+
+ @Test
+ public void testAppStateTimeSlotEvents() throws Exception {
+ final long maxTrackingDuration = 5_000L;
+ assertAppStateTimeSlotEvents(new int[] {2, 2, 0, 0, 1},
+ new long[] {1_500, 1_500, 2_100, 2_999, 5_999}, 5_000);
+ assertAppStateTimeSlotEvents(new int[] {2, 2, 0, 0, 1, 1},
+ new long[] {1_500, 1_500, 2_100, 2_999, 5_999, 6_000}, 6_000);
+ assertAppStateTimeSlotEvents(new int[] {2, 0, 0, 1, 1, 1},
+ new long[] {1_500, 1_500, 2_100, 2_999, 5_999, 6_000, 7_000}, 7_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {}, new long[] {}, new long[] {}, 0);
+ assertMergeAppStateTimeSlotEvents(new int[] {1}, new long[] {}, new long[] {1_500}, 1_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1}, new long[] {1_500}, new long[] {}, 1_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 1},
+ new long[] {1_500}, new long[] {2_500}, 2_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 1},
+ new long[] {2_500}, new long[] {1_500}, 2_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+ new long[] {1_500, 2_500}, new long[] {2_600, 3_000}, 3_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {2, 1, 1},
+ new long[] {2_600, 3_500}, new long[] {1_500, 1_600}, 3_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+ new long[] {1_500, 3_500}, new long[] {2_600, 2_700}, 3_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 2, 1},
+ new long[] {2_500, 2_600}, new long[] {1_500, 3_700}, 3_000);
+ assertMergeAppStateTimeSlotEvents(new int[] {1, 0, 0, 0, 0, 1},
+ new long[] {2_500, 8_600}, new long[] {1_500, 3_700}, 8_000);
+ }
+
+ private BaseAppStateTimeSlotEvents createBaseAppStateTimeSlotEvents(
+ long slotSize, long maxTrackingDuration, long[] timestamps) {
+ final BaseAppStateTimeSlotEvents testObj = new BaseAppStateTimeSlotEvents(
+ 0, "", 1, slotSize, "", () -> maxTrackingDuration) {};
+ for (int i = 0; i < timestamps.length; i++) {
+ testObj.addEvent(timestamps[i], 0);
+ }
+ return testObj;
+ }
+
+ private void assertAppStateTimeSlotEvents(int[] expectedEvents, long[] timestamps,
+ long expectedCurTimeslot) {
+ final BaseAppStateTimeSlotEvents testObj = createBaseAppStateTimeSlotEvents(1_000L,
+ 5_000L, timestamps);
+ assertEquals(expectedCurTimeslot, testObj.getCurrentSlotStartTime(0));
+ assertListEquals(createIntLinkedList(expectedEvents), testObj.getRawEvents(0));
+ }
+
+ private void assertMergeAppStateTimeSlotEvents(int[] expectedEvents, long[] timestamps1,
+ long[] timestamps2, long expectedCurTimeslot) {
+ final BaseAppStateTimeSlotEvents testObj1 = createBaseAppStateTimeSlotEvents(1_000L,
+ 5_000L, timestamps1);
+ final BaseAppStateTimeSlotEvents testObj2 = createBaseAppStateTimeSlotEvents(1_000L,
+ 5_000L, timestamps2);
+ testObj1.add(testObj2);
+ assertEquals(expectedCurTimeslot, testObj1.getCurrentSlotStartTime(0));
+ assertListEquals(createIntLinkedList(expectedEvents), testObj1.getRawEvents(0));
+ }
+
+ @Test
+ public void testMergeUidBatteryUsage() throws Exception {
+ final UidBatteryStates testObj = new UidBatteryStates(0, "", null);
+ assertListEquals(null, testObj.add(null, null));
+ assertListEquals(new LinkedList<UidStateEventWithBattery>(), testObj.add(
+ null, new LinkedList<UidStateEventWithBattery>()));
+ assertListEquals(new LinkedList<UidStateEventWithBattery>(), testObj.add(
+ new LinkedList<UidStateEventWithBattery>(), null));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ new LinkedList<UidStateEventWithBattery>()));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ testObj.add(new LinkedList<UidStateEventWithBattery>(),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {11L}, new double[] {11.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false}, new long[] {11L, 12L}, new double[] {11.0d, 1.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true}, new long[] {11L, 12L, 13L},
+ new double[] {11.0d, 1.0d, 13.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true}, new long[] {10L}, new double[] {10.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false}, new long[] {10L, 13L}, new double[] {10.0d, 3.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false}, new long[] {11L, 13L}, new double[] {11.0d, 2.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false}, new long[] {10L, 12L}, new double[] {10.0d, 2.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true}, new long[] {10L, 13L, 14L},
+ new double[] {10.0d, 3.0d, 14.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true}, new long[] {11L, 13L, 14L},
+ new double[] {11.0d, 2.0d, 14.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false}, new long[] {10L, 12L}, new double[] {10.0d, 2.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {10L, 13L, 14L, 17L, 18L, 21L},
+ new double[] {10.0d, 3.0d, 14.0d, 3.0d, 18.0d, 3.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {11L, 13L, 15L, 17L, 19L, 21L},
+ new double[] {11.0d, 2.0d, 15.0d, 2.0d, 19.0d, 2.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {10L, 12L, 14L, 16L, 18L, 20L},
+ new double[] {10.0d, 2.0d, 14.0d, 2.0d, 18.0d, 2.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false, true, false, true, false},
+ new long[] {10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L, 19L},
+ new double[] {10.0d, 1.0d, 12.0d, 1.0d, 14.0d, 1.0d, 16.0d, 1.0d, 18.0d, 1.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false},
+ new long[] {12L, 13L, 16L, 17L},
+ new double[] {12.0d, 1.0d, 16.0d, 1.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {10L, 11L, 14L, 15L, 18L, 19L},
+ new double[] {10.0d, 1.0d, 14.0d, 1.0d, 18.0d, 1.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false},
+ new long[] {10L, 14L, 18L, 19L},
+ new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false},
+ new long[] {11L, 12L, 13L, 14L},
+ new double[] {11.0d, 1.0d, 13.0d, 1.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {10L, 11L, 12L, 13L, 18L, 19L},
+ new double[] {10.0d, 1.0d, 12.0d, 1.0d, 18.0d, 1.0d})));
+ assertListEquals(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false},
+ new long[] {10L, 14L, 18L, 19L},
+ new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+ testObj.add(createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false},
+ new long[] {10L, 14L, 18L, 19L},
+ new double[] {10.0d, 4.0d, 18.0d, 1.0d}),
+ createUidStateEventWithBatteryList(
+ new boolean[] {true, false, true, false, true, false},
+ new long[] {10L, 11L, 12L, 13L, 18L, 19L},
+ new double[] {10.0d, 1.0d, 12.0d, 1.0d, 18.0d, 1.0d})));
+ }
+
+ private LinkedList<UidStateEventWithBattery> createUidStateEventWithBatteryList(
+ boolean[] isStart, long[] timestamps, double[] batteryUsage) {
+ final LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
+ for (int i = 0; i < isStart.length; i++) {
+ result.add(new UidStateEventWithBattery(
+ isStart[i], timestamps[i], batteryUsage[i], null));
+ }
+ return result;
+ }
+
+ private class TestBgRestrictionInjector extends AppRestrictionController.Injector {
+ private Context mContext;
+
+ TestBgRestrictionInjector(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ void initAppStateTrackers(AppRestrictionController controller) {
+ try {
+ mAppBatteryTracker = new AppBatteryTracker(mContext, controller,
+ TestAppBatteryTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppBatteryTracker);
+ mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller,
+ TestAppBatteryExemptionTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppBatteryExemptionTracker);
+ mAppFGSTracker = new AppFGSTracker(mContext, controller,
+ TestAppFGSTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppFGSTracker);
+ mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller,
+ TestAppMediaSessionTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppMediaSessionTracker);
+ mAppBroadcastEventsTracker = new AppBroadcastEventsTracker(mContext, controller,
+ TestAppBroadcastEventsTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppBroadcastEventsTracker);
+ mAppBindServiceEventsTracker = new AppBindServiceEventsTracker(mContext, controller,
+ TestAppBindServiceEventsTrackerInjector.class.getDeclaredConstructor(
+ BackgroundRestrictionTest.class),
+ BackgroundRestrictionTest.this);
+ controller.addAppStateTracker(mAppBindServiceEventsTracker);
+ } catch (NoSuchMethodException e) {
+ // Won't happen.
+ }
+ }
+
+ @Override
+ ActivityManagerInternal getActivityManagerInternal() {
+ return mActivityManagerInternal;
+ }
+
+ @Override
+ AppRestrictionController getAppRestrictionController() {
+ return mBgRestrictionController;
+ }
+
+ @Override
+ AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+
+ @Override
+ AppStandbyInternal getAppStandbyInternal() {
+ return mAppStandbyInternal;
+ }
+
+ @Override
+ AppHibernationManagerInternal getAppHibernationInternal() {
+ return mAppHibernationInternal;
+ }
+
+ @Override
+ AppStateTracker getAppStateTracker() {
+ return mAppStateTracker;
+ }
+
+ @Override
+ IActivityManager getIActivityManager() {
+ return mIActivityManager;
+ }
+
+ @Override
+ UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
+ @Override
+ PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ NotificationManager getNotificationManager() {
+ return mNotificationManager;
+ }
+
+ @Override
+ RoleManager getRoleManager() {
+ return mRoleManager;
+ }
+
+ @Override
+ AppFGSTracker getAppFGSTracker() {
+ return mAppFGSTracker;
+ }
+
+ @Override
+ AppMediaSessionTracker getAppMediaSessionTracker() {
+ return mAppMediaSessionTracker;
+ }
+
+ @Override
+ ActivityManagerService getActivityManagerService() {
+ return mActivityManagerService;
+ }
+
+ @Override
+ UidBatteryUsageProvider getUidBatteryUsageProvider() {
+ return mAppBatteryTracker;
+ }
+
+ @Override
+ AppBatteryExemptionTracker getAppBatteryExemptionTracker() {
+ return mAppBatteryExemptionTracker;
+ }
+ }
+
+ private class TestBaseTrackerInjector<T extends BaseAppStatePolicy>
+ extends BaseAppStateTracker.Injector<T> {
+ @Override
+ void onSystemReady() {
+ getPolicy().onSystemReady();
+ }
+
+ @Override
+ ActivityManagerInternal getActivityManagerInternal() {
+ return BackgroundRestrictionTest.this.mActivityManagerInternal;
+ }
+
+ @Override
+ BatteryManagerInternal getBatteryManagerInternal() {
+ return BackgroundRestrictionTest.this.mBatteryManagerInternal;
+ }
+
+ @Override
+ BatteryStatsInternal getBatteryStatsInternal() {
+ return BackgroundRestrictionTest.this.mBatteryStatsInternal;
+ }
+
+ @Override
+ DeviceIdleInternal getDeviceIdleInternal() {
+ return BackgroundRestrictionTest.this.mDeviceIdleInternal;
+ }
+
+ @Override
+ UserManagerInternal getUserManagerInternal() {
+ return BackgroundRestrictionTest.this.mUserManagerInternal;
+ }
+
+ @Override
+ long currentTimeMillis() {
+ return BackgroundRestrictionTest.this.mCurrentTimeMillis;
+ }
+
+ @Override
+ PackageManager getPackageManager() {
+ return BackgroundRestrictionTest.this.mPackageManager;
+ }
+
+ @Override
+ PermissionManagerServiceInternal getPermissionManagerServiceInternal() {
+ return BackgroundRestrictionTest.this.mPermissionManagerServiceInternal;
+ }
+
+ @Override
+ AppOpsManager getAppOpsManager() {
+ return BackgroundRestrictionTest.this.mAppOpsManager;
+ }
+
+ @Override
+ MediaSessionManager getMediaSessionManager() {
+ return BackgroundRestrictionTest.this.mMediaSessionManager;
+ }
+
+ @Override
+ long getServiceStartForegroundTimeout() {
+ return 1_000; // ms
+ }
+
+ @Override
+ RoleManager getRoleManager() {
+ return BackgroundRestrictionTest.this.mRoleManager;
+ }
+ }
+
+ private class TestAppBatteryTrackerInjector extends TestBaseTrackerInjector<AppBatteryPolicy> {
+ @Override
+ void setPolicy(AppBatteryPolicy policy) {
+ super.setPolicy(policy);
+ BackgroundRestrictionTest.this.mAppBatteryPolicy = policy;
+ }
+ }
+
+ private class TestAppBatteryExemptionTrackerInjector
+ extends TestBaseTrackerInjector<AppBatteryExemptionPolicy> {
+ }
+
+ private class TestAppFGSTrackerInjector extends TestBaseTrackerInjector<AppFGSPolicy> {
+ }
+
+ private class TestAppMediaSessionTrackerInjector
+ extends TestBaseTrackerInjector<AppMediaSessionPolicy> {
+ }
+
+ private class TestAppBroadcastEventsTrackerInjector
+ extends TestBaseTrackerInjector<AppBroadcastEventsPolicy> {
+ @Override
+ void setPolicy(AppBroadcastEventsPolicy policy) {
+ super.setPolicy(policy);
+ policy.setTimeSlotSize(1_000L);
+ }
+ }
+
+ private class TestAppBindServiceEventsTrackerInjector
+ extends TestBaseTrackerInjector<AppBindServiceEventsPolicy> {
+ @Override
+ void setPolicy(AppBindServiceEventsPolicy policy) {
+ super.setPolicy(policy);
+ policy.setTimeSlotSize(1_000L);
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 0198253..eed2d42 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -35,6 +35,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -45,6 +46,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.SystemService;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -167,7 +170,8 @@
}
private void startUser(GameManagerService gameManagerService, int userId) {
- gameManagerService.onUserStarting(userId);
+ UserInfo userInfo = new UserInfo(userId, "name", 0);
+ gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
mTestLooper.dispatchAll();
}
@@ -861,7 +865,7 @@
public void testInterventionAllowAngleFalse() throws Exception {
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
- gameManagerService.onUserStarting(USER_ID_1);
+ startUser(gameManagerService, USER_ID_1);
mockDeviceConfigPerformanceEnableAngle();
mockInterventionAllowAngleFalse();
mockModifyGameModeGranted();
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
index 0545fde..1c480ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
@@ -207,12 +207,12 @@
}
private void seedNoConfigurationForUser(SystemService.TargetUser user) {
- when(mMockGameServiceProviderSelector.get(user)).thenReturn(null);
+ when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(null);
}
private FakeGameServiceProviderInstance seedConfigurationForUser(SystemService.TargetUser user,
GameServiceProviderConfiguration configuration) {
- when(mMockGameServiceProviderSelector.get(user)).thenReturn(configuration);
+ when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(configuration);
FakeGameServiceProviderInstance instanceForConfiguration =
spy(new FakeGameServiceProviderInstance());
when(mMockGameServiceProviderInstanceFactory.create(configuration))
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
index 59d0970..23a6a49 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
@@ -16,6 +16,7 @@
package com.android.server.app;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
@@ -25,7 +26,6 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -138,7 +138,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(null);
+ mGameServiceProviderSelector.get(null, null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -155,7 +155,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -171,7 +171,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -187,7 +187,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -201,7 +201,7 @@
seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -218,7 +218,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -234,7 +234,7 @@
"res/xml/game_service_metadata_wrong_first_tag.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
assertThat(gameServiceProviderConfiguration).isNull();
}
@@ -251,7 +251,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
new GameServiceProviderConfiguration(USER_HANDLE_10,
@@ -277,7 +277,7 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
new GameServiceProviderConfiguration(USER_HANDLE_10,
@@ -300,7 +300,31 @@
"res/xml/game_service_metadata_valid.xml");
GameServiceProviderConfiguration gameServiceProviderConfiguration =
- mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10));
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
+
+ GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
+ new GameServiceProviderConfiguration(USER_HANDLE_10,
+ GAME_SERVICE_COMPONENT,
+ GAME_SESSION_SERVICE_COMPONENT);
+ assertThat(gameServiceProviderConfiguration).isEqualTo(
+ expectedGameServiceProviderConfiguration);
+ }
+
+ @Test
+ public void get_overridePresent_returnsDeviceConfigGameServiceProvider()
+ throws Exception {
+ seedSystemGameServicePackageName("other.package");
+
+ seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
+ resolveInfo(GAME_SERVICE_B_SERVICE_INFO), resolveInfo(GAME_SERVICE_SERVICE_INFO));
+ seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
+ seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME,
+ GAME_SERVICE_META_DATA_RES_ID,
+ "res/xml/game_service_metadata_valid.xml");
+
+ GameServiceProviderConfiguration gameServiceProviderConfiguration =
+ mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10),
+ GAME_SERVICE_PACKAGE_NAME);
GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
new GameServiceProviderConfiguration(USER_HANDLE_10,
@@ -324,7 +348,7 @@
argThat(intent ->
intent != null
&& intent.getAction().equals(
- GameService.ACTION_GAME_SERVICE)
+ GameService.ACTION_GAME_SERVICE)
&& intent.getPackage().equals(gameServicePackageName)
),
anyInt(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index 38f01b5..64e8613 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,43 +16,47 @@
package com.android.server.sensorprivacy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
+import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.AppOpsManager;
-import android.app.AppOpsManagerInternal;
import android.content.Context;
-import android.content.pm.UserInfo;
+import android.hardware.SensorPrivacyManager;
import android.os.Environment;
-import android.telephony.TelephonyManager;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
-import com.android.server.SensorPrivacyService;
-import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
-import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.mockito.ArgumentCaptor;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
-import java.util.concurrent.CompletableFuture;
+import java.nio.file.StandardCopyOption;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -71,6 +75,10 @@
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
+ public static final String PERSISTENCE_FILE7 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 7);
+ public static final String PERSISTENCE_FILE8 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 8);
public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
@@ -81,176 +89,281 @@
public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
- private Context mContext;
- @Mock
- private AppOpsManager mMockedAppOpsManager;
- @Mock
- private AppOpsManagerInternal mMockedAppOpsManagerInternal;
- @Mock
- private UserManagerInternal mMockedUserManagerInternal;
- @Mock
- private ActivityManager mMockedActivityManager;
- @Mock
- private ActivityTaskManager mMockedActivityTaskManager;
- @Mock
- private TelephonyManager mMockedTelephonyManager;
+ Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ String mDataDir = mContext.getApplicationInfo().dataDir;
+
+ @Before
+ public void setUp() {
+ new File(mDataDir, "sensor_privacy.xml").delete();
+ new File(mDataDir, "sensor_privacy_impl.xml").delete();
+ }
@Test
- public void testServiceInit() throws IOException {
+ public void testMigration1() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration2() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertTrue(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 11, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 11, CAMERA));
+
+ assertTrue(ps.getState(SOFTWARE, 12, MICROPHONE).isEnabled());
+ assertNull(ps.getState(SOFTWARE, 12, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(HARDWARE, 11, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 11, CAMERA));
+ assertNull(ps.getState(HARDWARE, 12, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 12, CAMERA));
+ }
+
+ @Test
+ public void testMigration3() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
+
+ assertFalse(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration4() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertFalse(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration5() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration6() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ private PersistedState migrateFromFile(String fileName) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
-
try {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- spyOn(mContext);
+ doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
- doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mMockedUserManagerInternal)
- .when(() -> LocalServices.getService(UserManagerInternal.class));
- doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
- doReturn(mMockedActivityTaskManager)
- .when(mContext).getSystemService(ActivityTaskManager.class);
- doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
- TelephonyManager.class);
+ UserManagerInternal umi = mock(UserManagerInternal.class);
+ doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(new int[] {0}).when(umi).getUserIds();
- String dataDir = mContext.getApplicationInfo().dataDir;
- doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+ Files.copy(
+ mContext.getAssets().open(fileName),
+ new File(mDataDir, "sensor_privacy.xml").toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
- File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
- onDeviceFile.delete();
-
- // Try all files with one known user
- doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
- initServiceWithPersistenceFile(onDeviceFile, null);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
-
- // Try all files with two known users
- doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(10);
- initServiceWithPersistenceFile(onDeviceFile, null);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
-
+ return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
@Test
- public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+ public void testPersistence1Version2() throws IOException {
+ PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
+
+ assertEquals(1, ps.getState(SOFTWARE, 0, MICROPHONE).getState());
+ assertEquals(123L, ps.getState(SOFTWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(SOFTWARE, 0, CAMERA).getState());
+ assertEquals(123L, ps.getState(SOFTWARE, 0, CAMERA).getLastChange());
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
}
@Test
- public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+ public void testPersistence2Version2() throws IOException {
+ PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
+
+ assertEquals(1, ps.getState(HARDWARE, 0, MICROPHONE).getState());
+ assertEquals(1234L, ps.getState(HARDWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(HARDWARE, 0, CAMERA).getState());
+ assertEquals(1234L, ps.getState(HARDWARE, 0, CAMERA).getLastChange());
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
- @Test
- public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
- }
-
- @Test
- public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
- }
-
- private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
- boolean expectedMicState, boolean expectedCamState)
- throws IOException {
+ private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
-
try {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- spyOn(mContext);
+ doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
+ Files.copy(
+ mContext.getAssets().open(version2FilePath),
+ new File(mDataDir, "sensor_privacy_impl.xml").toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
- doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mMockedAppOpsManagerInternal)
- .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
- doReturn(mMockedUserManagerInternal)
- .when(() -> LocalServices.getService(UserManagerInternal.class));
- doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
- doReturn(mMockedActivityTaskManager)
- .when(mContext).getSystemService(ActivityTaskManager.class);
- doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
- TelephonyManager.class);
-
- String dataDir = mContext.getApplicationInfo().dataDir;
- doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
-
- File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
- onDeviceFile.delete();
-
- doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
-
- CompletableFuture<Boolean> micState = new CompletableFuture<>();
- CompletableFuture<Boolean> camState = new CompletableFuture<>();
- doAnswer(invocation -> {
- int code = invocation.getArgument(0);
- boolean restricted = invocation.getArgument(1);
- if (code == AppOpsManager.OP_RECORD_AUDIO) {
- micState.complete(restricted);
- } else if (code == AppOpsManager.OP_CAMERA) {
- camState.complete(restricted);
- }
- return null;
- }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
- any());
-
- initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
-
- Assert.assertTrue(micState.join() == expectedMicState);
- Assert.assertTrue(camState.join() == expectedCamState);
-
+ return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
- private void initServiceWithPersistenceFile(File onDeviceFile,
- String persistenceFilePath) throws IOException {
- initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+ @Test
+ public void testGetDefaultState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+ doReturn(null).when(persistedState).getState(anyInt(), anyInt(), anyInt());
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+ SensorState camState = sensorPrivacyStateController.getState(SOFTWARE, 0, CAMERA);
+
+ assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
+ assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
+ verify(persistedState, times(1)).getState(SOFTWARE, 0, MICROPHONE);
+ verify(persistedState, times(1)).getState(SOFTWARE, 0, CAMERA);
+ } finally {
+ mockitoSession.finishMocking();
+ }
}
- private void initServiceWithPersistenceFile(File onDeviceFile,
- String persistenceFilePath, int startingUserId) throws IOException {
- if (persistenceFilePath != null) {
- Files.copy(mContext.getAssets().open(persistenceFilePath),
- onDeviceFile.toPath());
+ @Test
+ public void testGetSetState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ SensorState sensorState = mock(SensorState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+ doReturn(sensorState).when(persistedState).getState(SOFTWARE, 0, MICROPHONE);
+ doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
+ doReturn(0L).when(sensorState).getLastChange();
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+
+ assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
+ assertEquals(0L, micState.getLastChange());
+ } finally {
+ mockitoSession.finishMocking();
}
- SensorPrivacyService service = new SensorPrivacyService(mContext);
- if (startingUserId != -1) {
- SystemService.TargetUser mockedTargetUser =
- ExtendedMockito.mock(SystemService.TargetUser.class);
- doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
- service.onUserStarting(mockedTargetUser);
+ }
+
+ @Test
+ public void testSetState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ sensorPrivacyStateController.setState(SOFTWARE, 0, MICROPHONE, true,
+ mock(Handler.class), changed -> {});
+
+ ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
+
+ verify(persistedState, times(1)).setState(eq(SOFTWARE), eq(0), eq(MICROPHONE),
+ captor.capture());
+ assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
+ } finally {
+ mockitoSession.finishMocking();
}
- onDeviceFile.delete();
+ }
+
+ private SensorPrivacyStateController getSensorPrivacyStateControllerImpl() {
+ SensorPrivacyStateControllerImpl.getInstance().resetForTestingImpl();
+ return SensorPrivacyStateControllerImpl.getInstance();
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index f24059c..a6c81a0 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -133,9 +133,11 @@
name: "servicestests-core-utils",
srcs: [
"src/com/android/server/pm/PackageSettingBuilder.java",
+ "src/com/android/server/am/DeviceConfigSession.java",
],
static_libs: [
"services.core",
+ "compatibility-device-util-axt",
],
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c0ad69f..564c4e4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -282,6 +282,14 @@
mIsAutomotive = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+
+ final String TEST_STRING = "{count, plural,\n"
+ + " =1 {Test for exactly 1 cert out of 4}\n"
+ + " other {Test for exactly # certs out of 4}\n"
+ + "}";
+ doReturn(TEST_STRING)
+ .when(mContext.resources)
+ .getString(R.string.ssl_ca_cert_warning);
}
private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -1784,9 +1792,6 @@
StringParceledListSlice oneCert = asSlice(new String[] {"1"});
StringParceledListSlice fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
- final String TEST_STRING = "Test for exactly 2 certs out of 4";
- doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
-
// Given that we have exactly one certificate installed,
when(getServices().keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
// when that certificate is approved,
@@ -1802,9 +1807,10 @@
dpms.approveCaCert(fourCerts.getList().get(0), userId, true);
dpms.approveCaCert(fourCerts.getList().get(1), userId, true);
// a notification should be shown saying that there are two certificates left to approve.
+ final String TEST_STRING_RESULT = "Test for exactly 2 certs out of 4";
verify(getServices().notificationManager, timeout(1000))
.notifyAsUser(anyString(), anyInt(), argThat(hasExtra(EXTRA_TITLE,
- TEST_STRING
+ TEST_STRING_RESULT
)), eq(user));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ea03250..efc9a49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -106,7 +106,6 @@
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
-import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Point;
@@ -1666,34 +1665,6 @@
}
@Test
- public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- mDisplayContent.setFixedRotationLaunchingApp(activity,
- (mDisplayContent.getRotation() + 1) % 4);
- // Create a window so FixedRotationAdjustmentsItem can be sent.
- createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin");
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity2.setVisible(false);
- clearInvocations(mAtm.getLifecycleManager());
- // The first activity has applied fixed rotation but the second activity becomes the top
- // before the transition is done and it has the same rotation as display, so the dispatched
- // rotation adjustment of first activity must be cleared.
- mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2,
- false /* checkOpening */);
-
- final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor =
- ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
- verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
- eq(activity.app.getThread()), adjustmentsCaptor.capture());
- // The transformation is kept for animation in real case.
- assertTrue(activity.hasFixedRotationTransform());
- final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
- activity.token, null /* fixedRotationAdjustments */);
- // The captor may match other items. The first one must be the item to clear adjustments.
- assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0));
- }
-
- @Test
public void testRemoteRotation() {
DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 670eb75..d487113 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -30,8 +30,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.app.ActivityThread;
@@ -71,6 +72,8 @@
private IWindowManager mIWindowManager;
private DisplayManagerGlobal mDisplayManagerGlobal;
+ private static final int WAIT_TIMEOUT_MS = 1000;
+
@Before
public void setUp() throws Exception {
// Let the Display be created with the DualDisplay policy.
@@ -142,19 +145,22 @@
mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
- verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+ verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
- verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+ verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
+ // Clear the previous invocation histories in case we may count the previous
+ // onConfigurationChanged invocation into the next verification.
+ clearInvocations(tokenClient, imeContainer);
mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
- verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+ verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
- verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+ verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
diff --git a/services/usage/java/com/android/server/usage/BroadcastEvent.java b/services/usage/java/com/android/server/usage/BroadcastEvent.java
new file mode 100644
index 0000000..ceb79c1
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/BroadcastEvent.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+
+import java.util.Objects;
+
+/**
+ * Contains the data needed to identify a broadcast event.
+ */
+class BroadcastEvent {
+ private int mSourceUid;
+ private String mTargetPackage;
+ private int mTargetUserId;
+ private long mIdForResponseEvent;
+
+ BroadcastEvent(int sourceUid, @NonNull String targetPackage, @UserIdInt int targetUserId,
+ long idForResponseEvent) {
+ mSourceUid = sourceUid;
+ mTargetPackage = targetPackage;
+ mTargetUserId = targetUserId;
+ mIdForResponseEvent = idForResponseEvent;
+ }
+
+ public int getSourceUid() {
+ return mSourceUid;
+ }
+
+ public @NonNull String getTargetPackage() {
+ return mTargetPackage;
+ }
+
+ public @UserIdInt int getTargetUserId() {
+ return mTargetUserId;
+ }
+
+ public long getIdForResponseEvent() {
+ return mIdForResponseEvent;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || !(obj instanceof BroadcastEvent)) {
+ return false;
+ }
+ final BroadcastEvent other = (BroadcastEvent) obj;
+ return this.mSourceUid == other.mSourceUid
+ && this.mIdForResponseEvent == other.mIdForResponseEvent
+ && this.mTargetUserId == other.mTargetUserId
+ && this.mTargetPackage.equals(other.mTargetPackage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSourceUid, mTargetPackage, mTargetUserId,
+ mIdForResponseEvent);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "BroadcastEvent {"
+ + "srcUid=" + mSourceUid
+ + ",tgtPkg=" + mTargetPackage
+ + ",tgtUser=" + mTargetUserId
+ + ",id=" + mIdForResponseEvent
+ + "}";
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
new file mode 100644
index 0000000..aa01f31
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.usage.BroadcastResponseStats;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class BroadcastResponseStatsTracker {
+ private static final String TAG = "ResponseStatsTracker";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"NOTIFICATION_EVENT"}, value = {
+ NOTIFICATION_EVENT_POSTED,
+ NOTIFICATION_EVENT_UPDATED,
+ NOTIFICATION_EVENT_CANCELLED
+ })
+ public @interface NotificationEvent {}
+
+ private static final int NOTIFICATION_EVENT_POSTED = 0;
+ private static final int NOTIFICATION_EVENT_UPDATED = 1;
+ private static final int NOTIFICATION_EVENT_CANCELLED = 2;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Contains the mapping of user -> UserBroadcastEvents data.
+ */
+ @GuardedBy("mLock")
+ private SparseArray<UserBroadcastEvents> mUserBroadcastEvents = new SparseArray<>();
+
+ /**
+ * Contains the mapping of sourceUid -> {targetUser -> UserBroadcastResponseStats} data.
+ * Here sourceUid refers to the uid that sent a broadcast and targetUser is the user that the
+ * broadcast was directed to.
+ */
+ @GuardedBy("mLock")
+ private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats =
+ new SparseArray<>();
+
+ // TODO (206518114): Move all callbacks handling to a handler thread.
+ void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
+ UserHandle targetUser, long idForResponseEvent,
+ @ElapsedRealtimeLong long timestampMs) {
+ synchronized (mLock) {
+ final LongSparseArray<BroadcastEvent> broadcastEvents =
+ getOrCreateBroadcastEventsLocked(targetPackage, targetUser);
+ final BroadcastEvent broadcastEvent = new BroadcastEvent(
+ sourceUid, targetPackage, targetUser.getIdentifier(), idForResponseEvent);
+ broadcastEvents.append(timestampMs, broadcastEvent);
+ final BroadcastResponseStats responseStats =
+ getOrCreateBroadcastResponseStats(broadcastEvent);
+ responseStats.incrementBroadcastsDispatchedCount(1);
+ }
+ }
+
+ void reportNotificationPosted(@NonNull String packageName, UserHandle user,
+ @ElapsedRealtimeLong long timestampMs) {
+ reportNotificationEvent(NOTIFICATION_EVENT_POSTED, packageName, user, timestampMs);
+ }
+
+ void reportNotificationUpdated(@NonNull String packageName, UserHandle user,
+ @ElapsedRealtimeLong long timestampMs) {
+ reportNotificationEvent(NOTIFICATION_EVENT_UPDATED, packageName, user, timestampMs);
+
+ }
+
+ void reportNotificationCancelled(@NonNull String packageName, UserHandle user,
+ @ElapsedRealtimeLong long timestampMs) {
+ reportNotificationEvent(NOTIFICATION_EVENT_CANCELLED, packageName, user, timestampMs);
+ }
+
+ private void reportNotificationEvent(@NotificationEvent int event,
+ @NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ // TODO (206518114): Store last N events to dump for debugging purposes.
+ synchronized (mLock) {
+ final LongSparseArray<BroadcastEvent> broadcastEvents =
+ getBroadcastEventsLocked(packageName, user);
+ if (broadcastEvents == null) {
+ return;
+ }
+ // TODO (206518114): Add LongSparseArray.removeAtRange()
+ for (int i = broadcastEvents.size() - 1; i >= 0; --i) {
+ final long dispatchTimestampMs = broadcastEvents.keyAt(i);
+ final long elapsedDurationMs = timestampMs - dispatchTimestampMs;
+ if (elapsedDurationMs <= 0) {
+ continue;
+ }
+ if (dispatchTimestampMs >= timestampMs) {
+ continue;
+ }
+ // TODO (206518114): Make the constant configurable.
+ if (elapsedDurationMs <= 2 * 60 * 1000) {
+ final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i);
+ final BroadcastResponseStats responseStats =
+ getBroadcastResponseStats(broadcastEvent);
+ if (responseStats == null) {
+ continue;
+ }
+ switch (event) {
+ case NOTIFICATION_EVENT_POSTED:
+ responseStats.incrementNotificationsPostedCount(1);
+ break;
+ case NOTIFICATION_EVENT_UPDATED:
+ responseStats.incrementNotificationsUpdatedCount(1);
+ break;
+ case NOTIFICATION_EVENT_CANCELLED:
+ responseStats.incrementNotificationsCancelledCount(1);
+ break;
+ default:
+ Slog.wtf(TAG, "Unknown event: " + event);
+ }
+ }
+ broadcastEvents.removeAt(i);
+ }
+ }
+ }
+
+ @NonNull BroadcastResponseStats queryBroadcastResponseStats(int callingUid,
+ @NonNull String packageName, long id, @UserIdInt int userId) {
+ final BroadcastResponseStats aggregatedResponseStats =
+ new BroadcastResponseStats(packageName);
+ synchronized (mLock) {
+ final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
+ mUserResponseStats.get(callingUid);
+ if (responseStatsForCaller == null) {
+ return aggregatedResponseStats;
+ }
+ final UserBroadcastResponseStats responseStatsForUser =
+ responseStatsForCaller.get(userId);
+ if (responseStatsForUser == null) {
+ return aggregatedResponseStats;
+ }
+ responseStatsForUser.aggregateBroadcastResponseStats(aggregatedResponseStats,
+ packageName, id);
+ }
+ return aggregatedResponseStats;
+ }
+
+ void clearBroadcastResponseStats(int callingUid, @NonNull String packageName, long id,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
+ mUserResponseStats.get(callingUid);
+ if (responseStatsForCaller == null) {
+ return;
+ }
+ final UserBroadcastResponseStats responseStatsForUser =
+ responseStatsForCaller.get(userId);
+ if (responseStatsForUser == null) {
+ return;
+ }
+ responseStatsForUser.clearBroadcastResponseStats(packageName, id);
+ }
+ }
+
+ void onUserRemoved(@UserIdInt int userId) {
+ synchronized (mLock) {
+ mUserBroadcastEvents.remove(userId);
+
+ for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
+ mUserResponseStats.valueAt(i).remove(userId);
+ }
+ }
+ }
+
+ void onPackageRemoved(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
+ if (userBroadcastEvents != null) {
+ userBroadcastEvents.onPackageRemoved(packageName);
+ }
+
+ for (int i = mUserResponseStats.size() - 1; i >= 0; --i) {
+ final UserBroadcastResponseStats userResponseStats =
+ mUserResponseStats.valueAt(i).get(userId);
+ if (userResponseStats != null) {
+ userResponseStats.onPackageRemoved(packageName);
+ }
+ }
+ }
+ }
+
+ void onUidRemoved(int uid) {
+ synchronized (mLock) {
+ for (int i = mUserBroadcastEvents.size() - 1; i >= 0; --i) {
+ mUserBroadcastEvents.valueAt(i).onUidRemoved(uid);
+ }
+
+ mUserResponseStats.remove(uid);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private LongSparseArray<BroadcastEvent> getBroadcastEventsLocked(
+ @NonNull String packageName, UserHandle user) {
+ final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(
+ user.getIdentifier());
+ if (userBroadcastEvents == null) {
+ return null;
+ }
+ return userBroadcastEvents.getBroadcastEvents(packageName);
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private LongSparseArray<BroadcastEvent> getOrCreateBroadcastEventsLocked(
+ @NonNull String packageName, UserHandle user) {
+ UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(user.getIdentifier());
+ if (userBroadcastEvents == null) {
+ userBroadcastEvents = new UserBroadcastEvents();
+ mUserBroadcastEvents.put(user.getIdentifier(), userBroadcastEvents);
+ }
+ return userBroadcastEvents.getOrCreateBroadcastEvents(packageName);
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private BroadcastResponseStats getBroadcastResponseStats(
+ @NonNull BroadcastEvent broadcastEvent) {
+ final int sourceUid = broadcastEvent.getSourceUid();
+ final SparseArray<UserBroadcastResponseStats> responseStatsForUid =
+ mUserResponseStats.get(sourceUid);
+ return getBroadcastResponseStats(responseStatsForUid, broadcastEvent);
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private BroadcastResponseStats getBroadcastResponseStats(
+ @Nullable SparseArray<UserBroadcastResponseStats> responseStatsForUid,
+ @NonNull BroadcastEvent broadcastEvent) {
+ if (responseStatsForUid == null) {
+ return null;
+ }
+ final UserBroadcastResponseStats userResponseStats = responseStatsForUid.get(
+ broadcastEvent.getTargetUserId());
+ if (userResponseStats == null) {
+ return null;
+ }
+ return userResponseStats.getBroadcastResponseStats(broadcastEvent);
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private BroadcastResponseStats getOrCreateBroadcastResponseStats(
+ @NonNull BroadcastEvent broadcastEvent) {
+ final int sourceUid = broadcastEvent.getSourceUid();
+ SparseArray<UserBroadcastResponseStats> userResponseStatsForUid =
+ mUserResponseStats.get(sourceUid);
+ if (userResponseStatsForUid == null) {
+ userResponseStatsForUid = new SparseArray<>();
+ mUserResponseStats.put(sourceUid, userResponseStatsForUid);
+ }
+ UserBroadcastResponseStats userResponseStats = userResponseStatsForUid.get(
+ broadcastEvent.getTargetUserId());
+ if (userResponseStats == null) {
+ userResponseStats = new UserBroadcastResponseStats();
+ userResponseStatsForUid.put(broadcastEvent.getTargetUserId(), userResponseStats);
+ }
+ return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent);
+ }
+
+ void dump(@NonNull IndentingPrintWriter ipw) {
+ ipw.println("Broadcast response stats:");
+ ipw.increaseIndent();
+
+ synchronized (mLock) {
+ dumpBroadcastEventsLocked(ipw);
+ ipw.println();
+ dumpResponseStatsLocked(ipw);
+ }
+
+ ipw.decreaseIndent();
+ }
+
+ @GuardedBy("mLock")
+ private void dumpBroadcastEventsLocked(@NonNull IndentingPrintWriter ipw) {
+ ipw.println("Broadcast events:");
+ ipw.increaseIndent();
+ for (int i = 0; i < mUserBroadcastEvents.size(); ++i) {
+ final int userId = mUserBroadcastEvents.keyAt(i);
+ final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.valueAt(i);
+ ipw.println("User " + userId + ":");
+ ipw.increaseIndent();
+ userBroadcastEvents.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+
+ @GuardedBy("mLock")
+ private void dumpResponseStatsLocked(@NonNull IndentingPrintWriter ipw) {
+ ipw.println("Response stats:");
+ ipw.increaseIndent();
+ for (int i = 0; i < mUserResponseStats.size(); ++i) {
+ final int sourceUid = mUserResponseStats.keyAt(i);
+ final SparseArray<UserBroadcastResponseStats> userBroadcastResponseStats =
+ mUserResponseStats.valueAt(i);
+ ipw.println("Uid " + sourceUid + ":");
+ ipw.increaseIndent();
+ for (int j = 0; j < userBroadcastResponseStats.size(); ++j) {
+ final int userId = userBroadcastResponseStats.keyAt(j);
+ final UserBroadcastResponseStats broadcastResponseStats =
+ userBroadcastResponseStats.valueAt(j);
+ ipw.println("User " + userId + ":");
+ ipw.increaseIndent();
+ broadcastResponseStats.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+}
+
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 559eb38..e28839e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -29,13 +29,17 @@
import static android.app.usage.UsageEvents.Event.USER_UNLOCKED;
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import android.Manifest;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -44,6 +48,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
+import android.app.usage.BroadcastResponseStats;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
import android.app.usage.IUsageStatsManager;
@@ -217,6 +222,8 @@
private final CopyOnWriteArraySet<UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener>
mEstimatedLaunchTimeChangedListeners = new CopyOnWriteArraySet<>();
+ private BroadcastResponseStatsTracker mResponseStatsTracker;
+
private static class ActivityData {
private final String mTaskRootPackage;
private final String mTaskRootClass;
@@ -263,6 +270,7 @@
}
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onStart() {
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
@@ -271,6 +279,7 @@
mHandler = new H(BackgroundThread.get().getLooper());
mAppStandby = mInjector.getAppStandbyController(getContext());
+ mResponseStatsTracker = new BroadcastResponseStatsTracker();
mAppTimeLimit = new AppTimeLimitController(getContext(),
new AppTimeLimitController.TimeLimitCallbackListener() {
@@ -315,6 +324,9 @@
getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
null, mHandler);
+ getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
+ new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -536,6 +548,7 @@
if (Intent.ACTION_USER_REMOVED.equals(action)) {
if (userId >= 0) {
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
+ mResponseStatsTracker.onUserRemoved(userId);
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >= 0) {
@@ -545,6 +558,20 @@
}
}
+ private class UidRemovedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int uid = intent.getIntExtra(EXTRA_UID, -1);
+ if (uid == -1) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mResponseStatsTracker.onUidRemoved(uid);
+ }
+ }
+ }
+
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
@@ -1780,6 +1807,10 @@
}
return;
}
+ } else if ("broadcast-response-stats".equals(arg)) {
+ synchronized (mLock) {
+ mResponseStatsTracker.dump(idpw);
+ }
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
pkgs.add(arg);
@@ -1813,6 +1844,9 @@
idpw.println();
mAppTimeLimit.dump(null, pw);
+
+ idpw.println();
+ mResponseStatsTracker.dump(idpw);
}
mAppStandby.dumpUsers(idpw, userIds, pkgs);
@@ -2645,6 +2679,60 @@
/ TimeUnit.DAYS.toMillis(1) * TimeUnit.DAYS.toMillis(1);
}
}
+
+ @Override
+ @NonNull
+ public BroadcastResponseStats queryBroadcastResponseStats(
+ @NonNull String packageName,
+ @IntRange(from = 1) long id,
+ @NonNull String callingPackage,
+ @UserIdInt int userId) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(callingPackage);
+ // TODO: Move to Preconditions utility class
+ if (id <= 0) {
+ throw new IllegalArgumentException("id needs to be >0");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ if (!hasPermission(callingPackage)) {
+ throw new SecurityException(
+ "Caller does not have the permission needed to call this API; "
+ + "callingPackage=" + callingPackage
+ + ", callingUid=" + callingUid);
+ }
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
+ userId, false /* allowAll */, false /* requireFull */,
+ "queryBroadcastResponseStats" /* name */, callingPackage);
+ return mResponseStatsTracker.queryBroadcastResponseStats(
+ callingUid, packageName, id, userId);
+ }
+
+ @Override
+ public void clearBroadcastResponseStats(
+ @NonNull String packageName,
+ @IntRange(from = 1) long id,
+ @NonNull String callingPackage,
+ @UserIdInt int userId) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(callingPackage);
+ if (id <= 0) {
+ throw new IllegalArgumentException("id needs to be >0");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ if (!hasPermission(callingPackage)) {
+ throw new SecurityException(
+ "Caller does not have the permission needed to call this API; "
+ + "callingPackage=" + callingPackage
+ + ", callingUid=" + callingUid);
+ }
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
+ userId, false /* allowAll */, false /* requireFull */,
+ "clearBroadcastResponseStats" /* name */, callingPackage);
+ mResponseStatsTracker.clearBroadcastResponseStats(callingUid,
+ packageName, id, userId);
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
@@ -2953,21 +3041,26 @@
public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
@NonNull UserHandle targetUser, long idForResponseEvent,
@ElapsedRealtimeLong long timestampMs) {
+ mResponseStatsTracker.reportBroadcastDispatchEvent(sourceUid, targetPackage,
+ targetUser, idForResponseEvent, timestampMs);
}
@Override
public void reportNotificationPosted(@NonNull String packageName,
@NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ mResponseStatsTracker.reportNotificationPosted(packageName, user, timestampMs);
}
@Override
public void reportNotificationUpdated(@NonNull String packageName,
@NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ mResponseStatsTracker.reportNotificationUpdated(packageName, user, timestampMs);
}
@Override
public void reportNotificationRemoved(@NonNull String packageName,
@NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ mResponseStatsTracker.reportNotificationCancelled(packageName, user, timestampMs);
}
}
@@ -2980,6 +3073,7 @@
mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName)
.sendToTarget();
}
+ mResponseStatsTracker.onPackageRemoved(packageName, UserHandle.getUserId(uid));
super.onPackageRemoved(packageName, uid);
}
}
diff --git a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java
new file mode 100644
index 0000000..819644846
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+class UserBroadcastEvents {
+ /**
+ * Contains the mapping of targetPackage -> {BroadcastEvent} data.
+ * Here targetPackage refers to the package receiving the broadcast and BroadcastEvent objects
+ * corresponding to each broadcast it is receiving.
+ */
+ private ArrayMap<String, LongSparseArray<BroadcastEvent>> mBroadcastEvents = new ArrayMap();
+
+ @Nullable LongSparseArray<BroadcastEvent> getBroadcastEvents(@NonNull String packageName) {
+ return mBroadcastEvents.get(packageName);
+ }
+
+ @NonNull LongSparseArray<BroadcastEvent> getOrCreateBroadcastEvents(
+ @NonNull String packageName) {
+ LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.get(packageName);
+ if (broadcastEvents == null) {
+ broadcastEvents = new LongSparseArray<>();
+ mBroadcastEvents.put(packageName, broadcastEvents);
+ }
+ return broadcastEvents;
+ }
+
+ void onPackageRemoved(@NonNull String packageName) {
+ mBroadcastEvents.remove(packageName);
+ }
+
+ void onUidRemoved(int uid) {
+ for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) {
+ final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i);
+ for (int j = broadcastEvents.size() - 1; j >= 0; --j) {
+ if (broadcastEvents.valueAt(j).getSourceUid() == uid) {
+ broadcastEvents.removeAt(j);
+ }
+ }
+ }
+ }
+
+ void dump(@NonNull IndentingPrintWriter ipw) {
+ for (int i = 0; i < mBroadcastEvents.size(); ++i) {
+ final String packageName = mBroadcastEvents.keyAt(i);
+ final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i);
+ ipw.println(packageName + ":");
+ ipw.increaseIndent();
+ if (broadcastEvents.size() == 0) {
+ ipw.println("<empty>");
+ } else {
+ for (int j = 0; j < broadcastEvents.size(); ++j) {
+ final long timestampMs = broadcastEvents.keyAt(j);
+ final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(j);
+ TimeUtils.formatDuration(timestampMs, ipw);
+ ipw.print(": ");
+ ipw.println(broadcastEvent);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java
new file mode 100644
index 0000000..ac2a320
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.BroadcastResponseStats;
+import android.util.ArrayMap;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+class UserBroadcastResponseStats {
+ /**
+ * Contains the mapping of a BroadcastEvent type to it's aggregated stats.
+ */
+ private ArrayMap<BroadcastEvent, BroadcastResponseStats> mResponseStats =
+ new ArrayMap<>();
+
+ @Nullable BroadcastResponseStats getBroadcastResponseStats(
+ BroadcastEvent broadcastEvent) {
+ return mResponseStats.get(broadcastEvent);
+ }
+
+ @NonNull BroadcastResponseStats getOrCreateBroadcastResponseStats(
+ BroadcastEvent broadcastEvent) {
+ BroadcastResponseStats responseStats = mResponseStats.get(broadcastEvent);
+ if (responseStats == null) {
+ responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage());
+ mResponseStats.put(broadcastEvent, responseStats);
+ }
+ return responseStats;
+ }
+
+ void aggregateBroadcastResponseStats(
+ @NonNull BroadcastResponseStats responseStats,
+ @NonNull String packageName, long id) {
+ for (int i = mResponseStats.size() - 1; i >= 0; --i) {
+ final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
+ if (broadcastEvent.getIdForResponseEvent() == id
+ && broadcastEvent.getTargetPackage().equals(packageName)) {
+ responseStats.addCounts(mResponseStats.valueAt(i));
+ }
+ }
+ }
+
+ void clearBroadcastResponseStats(@NonNull String packageName, long id) {
+ for (int i = mResponseStats.size() - 1; i >= 0; --i) {
+ final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
+ if (broadcastEvent.getIdForResponseEvent() == id
+ && broadcastEvent.getTargetPackage().equals(packageName)) {
+ mResponseStats.removeAt(i);
+ }
+ }
+ }
+
+ void onPackageRemoved(@NonNull String packageName) {
+ for (int i = mResponseStats.size() - 1; i >= 0; --i) {
+ if (mResponseStats.keyAt(i).getTargetPackage().equals(packageName)) {
+ mResponseStats.removeAt(i);
+ }
+ }
+ }
+
+ void dump(@NonNull IndentingPrintWriter ipw) {
+ for (int i = 0; i < mResponseStats.size(); ++i) {
+ final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
+ final BroadcastResponseStats responseStats = mResponseStats.valueAt(i);
+ ipw.print(broadcastEvent);
+ ipw.print(" -> ");
+ ipw.println(responseStats);
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 9d4db00..85b1de5 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -41,6 +41,7 @@
private final boolean mIsInputHeadset;
private final boolean mIsOutputHeadset;
+ private final boolean mIsDock;
private boolean mSelected = false;
private int mOutputState;
@@ -53,7 +54,7 @@
public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
boolean hasOutput, boolean hasInput,
- boolean isInputHeadset, boolean isOutputHeadset) {
+ boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) {
mAudioService = audioService;
mCardNum = card;
mDeviceNum = device;
@@ -62,31 +63,32 @@
mHasInput = hasInput;
mIsInputHeadset = isInputHeadset;
mIsOutputHeadset = isOutputHeadset;
+ mIsDock = isDock;
}
/**
- * @returns the ALSA card number associated with this peripheral.
+ * @return the ALSA card number associated with this peripheral.
*/
public int getCardNum() {
return mCardNum;
}
/**
- * @returns the ALSA device number associated with this peripheral.
+ * @return the ALSA device number associated with this peripheral.
*/
public int getDeviceNum() {
return mDeviceNum;
}
/**
- * @returns the USB device device address associated with this peripheral.
+ * @return the USB device device address associated with this peripheral.
*/
public String getDeviceAddress() {
return mDeviceAddress;
}
/**
- * @returns the ALSA card/device address string.
+ * @return the ALSA card/device address string.
*/
public String getAlsaCardDeviceString() {
if (mCardNum < 0 || mDeviceNum < 0) {
@@ -98,35 +100,42 @@
}
/**
- * @returns true if the device supports output.
+ * @return true if the device supports output.
*/
public boolean hasOutput() {
return mHasOutput;
}
/**
- * @returns true if the device supports input (recording).
+ * @return true if the device supports input (recording).
*/
public boolean hasInput() {
return mHasInput;
}
/**
- * @returns true if the device is a headset for purposes of input.
+ * @return true if the device is a headset for purposes of input.
*/
public boolean isInputHeadset() {
return mIsInputHeadset;
}
/**
- * @returns true if the device is a headset for purposes of output.
+ * @return true if the device is a headset for purposes of output.
*/
public boolean isOutputHeadset() {
return mIsOutputHeadset;
}
/**
- * @returns true if input jack is detected or jack detection is not supported.
+ * @return true if the device is a USB dock.
+ */
+ public boolean isDock() {
+ return mIsDock;
+ }
+
+ /**
+ * @return true if input jack is detected or jack detection is not supported.
*/
private synchronized boolean isInputJackConnected() {
if (mJackDetector == null) {
@@ -136,7 +145,7 @@
}
/**
- * @returns true if input jack is detected or jack detection is not supported.
+ * @return true if input jack is detected or jack detection is not supported.
*/
private synchronized boolean isOutputJackConnected() {
if (mJackDetector == null) {
@@ -190,9 +199,10 @@
try {
// Output Device
if (mHasOutput) {
- int device = mIsOutputHeadset
- ? AudioSystem.DEVICE_OUT_USB_HEADSET
- : AudioSystem.DEVICE_OUT_USB_DEVICE;
+ int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+ : (mIsOutputHeadset
+ ? AudioSystem.DEVICE_OUT_USB_HEADSET
+ : AudioSystem.DEVICE_OUT_USB_DEVICE);
if (DEBUG) {
Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
+ " addr:" + alsaCardDeviceString
@@ -231,7 +241,7 @@
/**
* @Override
- * @returns a string representation of the object.
+ * @return a string representation of the object.
*/
public synchronized String toString() {
return "UsbAlsaDevice: [card: " + mCardNum
@@ -273,7 +283,7 @@
/**
* @Override
- * @returns true if the objects are equivalent.
+ * @return true if the objects are equivalent.
*/
public boolean equals(Object obj) {
if (!(obj instanceof UsbAlsaDevice)) {
@@ -285,12 +295,13 @@
&& mHasOutput == other.mHasOutput
&& mHasInput == other.mHasInput
&& mIsInputHeadset == other.mIsInputHeadset
- && mIsOutputHeadset == other.mIsOutputHeadset);
+ && mIsOutputHeadset == other.mIsOutputHeadset
+ && mIsDock == other.mIsDock);
}
/**
* @Override
- * @returns a hash code generated from the object contents.
+ * @return a hash code generated from the object contents.
*/
public int hashCode() {
final int prime = 31;
@@ -301,6 +312,7 @@
result = prime * result + (mHasInput ? 0 : 1);
result = prime * result + (mIsInputHeadset ? 0 : 1);
result = prime * result + (mIsOutputHeadset ? 0 : 1);
+ result = prime * result + (mIsDock ? 0 : 1);
return result;
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 1c72eb8..fd9b995 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -237,6 +237,7 @@
if (hasInput || hasOutput) {
boolean isInputHeadset = parser.isInputHeadset();
boolean isOutputHeadset = parser.isOutputHeadset();
+ boolean isDock = parser.isDock();
if (mAudioService == null) {
Slog.e(TAG, "no AudioService");
@@ -246,7 +247,7 @@
UsbAlsaDevice alsaDevice =
new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
deviceAddress, hasOutput, hasInput,
- isInputHeadset, isOutputHeadset);
+ isInputHeadset, isOutputHeadset, isDock);
if (alsaDevice != null) {
alsaDevice.setDeviceNameAndDescription(
cardRec.getCardName(), cardRec.getCardDescription());
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 9ac270f..94cc826 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -165,7 +165,7 @@
pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID())
+ " product:" + Integer.toHexString(deviceDescriptor.getProductID()));
pw.println("isHeadset[in: " + parser.isInputHeadset()
- + " , out: " + parser.isOutputHeadset() + "]");
+ + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
} else {
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
}
@@ -179,9 +179,8 @@
UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
descriptorTree.parse(parser);
descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
-
stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
- + " , out: " + parser.isOutputHeadset() + "]");
+ + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
pw.println(stringBuilder.toString());
} else {
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
@@ -198,9 +197,8 @@
descriptor.report(canvas);
}
pw.println(stringBuilder.toString());
-
pw.println("isHeadset[in: " + parser.isInputHeadset()
- + " , out: " + parser.isOutputHeadset() + "]");
+ + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
} else {
pw.println(formatTime() + " Disconnect " + mDeviceAddress);
}
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
index db0c80f..13d404c 100644
--- a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
@@ -218,14 +218,13 @@
if (doesInterfaceContainInput
&& doesInterfaceContainOutput) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
- if (!connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true)) {
- Log.d(TAG, "Can't claim control interface");
- continue;
- }
- int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
- interfaceDescriptor.getInterfaceNumber(),
- interfaceDescriptor.getAlternateSetting());
+ // The ALSA does not handle switching to the MIDI 2.0 interface correctly
+ // and stops exposing /dev/snd/midiC1D0 after calling connection.setInterface().
+ // Thus, simply use the control interface (interface zero).
+ int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
+ 0,
+ interfaceDescriptor.getAlternateSetting());
connection.close();
return defaultMidiProtocol;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 3412a6f..6e68a91 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -870,4 +870,35 @@
return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
}
+ /**
+ * isDock() indicates if the connected USB output peripheral is a docking station with
+ * audio output.
+ * A valid audio dock must declare only one audio output control terminal of type
+ * TERMINAL_EXTERN_DIGITAL.
+ */
+ public boolean isDock() {
+ if (hasMIDIInterface() || hasHIDInterface()) {
+ return false;
+ }
+
+ ArrayList<UsbDescriptor> acDescriptors =
+ getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
+ UsbACInterface.AUDIO_AUDIOCONTROL);
+
+ if (acDescriptors.size() != 1) {
+ return false;
+ }
+
+ if (acDescriptors.get(0) instanceof UsbACTerminal) {
+ UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0);
+ if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) {
+ return true;
+ }
+ } else {
+ Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength()
+ + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType()));
+ }
+ return false;
+ }
+
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index e19ea47..8acd3c7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,6 +51,7 @@
import android.os.SharedMemory;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
import android.service.voice.IHotwordDetectionService;
@@ -132,12 +133,13 @@
private @NonNull ServiceConnection mRemoteHotwordDetectionService;
private IBinder mAudioFlinger;
private boolean mDebugHotwordLogging = false;
+ private final int mDetectorType;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
- @NonNull IHotwordRecognitionStatusCallback callback) {
+ @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) {
if (callback == null) {
Slog.w(TAG, "Callback is null while creating connection");
throw new IllegalArgumentException("Callback is null while creating connection");
@@ -149,6 +151,7 @@
mDetectionComponentName = serviceName;
mUser = userId;
mCallback = callback;
+ mDetectorType = detectorType;
final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
intent.setComponent(mDetectionComponentName);
initAudioFlingerLocked();
@@ -657,7 +660,8 @@
pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
pw.print(", mPerformingSoftwareHotwordDetection=" + mPerformingSoftwareHotwordDetection);
pw.print(", mRestartCount=" + mServiceConnectionFactory.mRestartCount);
- pw.println(", mLastRestartInstant=" + mLastRestartInstant);
+ pw.print(", mLastRestartInstant=" + mLastRestartInstant);
+ pw.println(", mDetectorType=" + HotwordDetector.detectorTypeToString(mDetectorType));
}
private void handleExternalSourceHotwordDetection(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 8445ed4..1285a84 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1168,7 +1168,8 @@
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
- IHotwordRecognitionStatusCallback callback) {
+ IHotwordRecognitionStatusCallback callback,
+ int detectorType) {
enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
@@ -1184,7 +1185,7 @@
final long caller = Binder.clearCallingIdentity();
try {
mImpl.updateStateLocked(
- voiceInteractorIdentity, options, sharedMemory, callback);
+ voiceInteractorIdentity, options, sharedMemory, callback, detectorType);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 96c78bc..fb4d73c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -53,6 +53,7 @@
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.os.UserHandle;
+import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -102,6 +103,7 @@
VoiceInteractionSessionConnection mActiveSession;
int mDisabledShowContext;
+ int mDetectorType;
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -457,7 +459,8 @@
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
- IHotwordRecognitionStatusCallback callback) {
+ IHotwordRecognitionStatusCallback callback,
+ int detectorType) {
Slog.v(TAG, "updateStateLocked");
if (mHotwordDetectionComponentName == null) {
Slog.w(TAG, "Hotword detection service name not found");
@@ -494,11 +497,13 @@
throw new IllegalStateException("Can't set sharedMemory to be read-only");
}
+ mDetectorType = detectorType;
+
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
- options, sharedMemory, callback);
+ options, sharedMemory, callback, detectorType);
} else {
mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}
@@ -668,6 +673,8 @@
pw.println(Integer.toHexString(mDisabledShowContext));
}
pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService);
+ pw.print(" mDetectorType=");
+ pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
if (mHotwordDetectionConnection != null) {
pw.println(" Hotword detection connection:");
mHotwordDetectionConnection.dump(" ", pw);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index edb817e..baccb26 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11965,7 +11965,11 @@
private final int mErrorCode;
- /** @hide */
+ /**
+ * An exception with ModemActivityInfo specific error codes.
+ *
+ * @param errorCode a ModemActivityInfoError code.
+ */
public ModemActivityInfoException(@ModemActivityInfoError int errorCode) {
mErrorCode = errorCode;
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 409c838..2470887 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -114,6 +114,7 @@
public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 54;
public static final int EVENT_SIM_STATE_UPDATED = BASE + 55;
public static final int EVENT_APN_UNTHROTTLED = BASE + 56;
+ public static final int EVENT_TRAFFIC_DESCRIPTORS_UPDATED = BASE + 57;
/***** Constants *****/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 7443f0b..61df403 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -28,7 +28,10 @@
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -125,6 +128,21 @@
@Test
override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ assumeFalse(isShellTransitionsEnabled)
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+
+ @FlakyTest(bugId = 216266712)
+ @Test
+ fun appWindowReplacesLauncherAsTopWindow_shellTransit() {
+ assumeTrue(isShellTransitionsEnabled)
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+
companion object {
/**
* Creates the test configurations.
@@ -139,4 +157,4 @@
.getConfigNonRotationTests(repetitions = 5)
}
}
-}
\ No newline at end of file
+}