Merge "limit TelecomManager#registerPhoneAccount to 10; api doc update"
diff --git a/Android.bp b/Android.bp
index 8bc5d7a..d5d5150 100644
--- a/Android.bp
+++ b/Android.bp
@@ -155,32 +155,9 @@
name: "framework-all",
installable: false,
static_libs: [
- "android.net.ipsec.ike.impl",
+ "all-framework-module-impl",
"framework-minus-apex",
- "framework-appsearch.impl",
- "framework-connectivity.impl",
- "framework-connectivity-tiramisu.impl",
- "framework-graphics.impl",
- "framework-mediaprovider.impl",
- "framework-permission.impl",
- "framework-permission-s.impl",
- "framework-scheduling.impl",
- "framework-sdkextensions.impl",
- "framework-statsd.impl",
- "framework-supplementalprocess.impl",
- "framework-tethering.impl",
- "framework-nearby.impl",
- "framework-uwb.impl",
- "framework-wifi.impl",
- "updatable-media",
],
- soong_config_variables: {
- include_nonpublic_framework_api: {
- static_libs: [
- "framework-supplementalapi.impl",
- ],
- },
- },
apex_available: ["//apex_available:platform"],
sdk_version: "core_platform",
visibility: [
@@ -308,6 +285,8 @@
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
+ // TODO: remove when moved to the below package
+ "frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
],
},
@@ -416,7 +395,6 @@
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
- "framework-appsearch.impl", // TODO(b/146218515): should be removed
"framework-updatable-stubs-module_libs_api",
],
sdk_version: "core_platform",
@@ -557,6 +535,9 @@
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
+ // TODO: remove when moved to the below package
+ "frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
+ "packages/modules/Connectivity/framework/aidl-export",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/ApiDocs.bp b/ApiDocs.bp
index c50d446..b5acfb2 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,24 +67,20 @@
],
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}",
":i18n.module.public.api{.public.stubs.source}",
":framework-appsearch-sources",
+ ":framework-auxiliary-sources",
":framework-connectivity-sources",
":framework-bluetooth-sources",
":framework-connectivity-tiramisu-updatable-sources",
@@ -123,13 +103,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 +125,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..c4795f5 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,26 @@
* @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;
+ /**
+ * Carrier privileged app.
+ * @hide
+ */
+ public static final int REASON_CARRIER_PRIVILEGED_APP = 321;
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -404,7 +423,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 +442,10 @@
REASON_EVENT_MMS,
REASON_SHELL,
REASON_MEDIA_SESSION_CALLBACK,
+ REASON_ROLE_DIALER,
+ REASON_ROLE_EMERGENCY,
+ REASON_SYSTEM_MODULE,
+ REASON_CARRIER_PRIVILEGED_APP,
REASON_OPT_OUT_REQUESTED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -688,8 +711,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 +749,14 @@
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_CARRIER_PRIVILEGED_APP:
+ return "CARRIER_PRIVILEGED_APP";
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..13ecd25 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -3,9 +3,10 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
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 +153,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 +170,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);
@@ -194,4 +217,19 @@
void dumpState(String[] args, PrintWriter pw);
boolean isAppIdleEnabled();
+
+ /**
+ * Returns the duration (in millis) for the window where events occurring will be
+ * considered as broadcast response, starting from the point when an app receives
+ * a broadcast.
+ */
+ long getBroadcastResponseWindowDurationMs();
+
+ /**
+ * Returns the process state threshold that should be used for deciding whether or not an app
+ * is in the background in the context of recording broadcast response stats. Apps whose
+ * process state is higher than this threshold state should be considered to be in background.
+ */
+ @ProcessState
+ int getBroadcastResponseFgThresholdState();
}
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..cea1945 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -357,6 +357,9 @@
// Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping
// (ScheduledJobStateChanged and JobStatusDumpProto).
public static final int RESTRICTED_INDEX = 5;
+ // Putting EXEMPTED_INDEX after RESTRICTED_INDEX to make it easier for proto dumping
+ // (ScheduledJobStateChanged and JobStatusDumpProto).
+ public static final int EXEMPTED_INDEX = 6;
private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener,
EconomyManagerInternal.TareStateChangeListener {
@@ -1209,18 +1212,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
@@ -2494,6 +2495,7 @@
shouldForceBatchJob =
mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
&& job.getEffectiveStandbyBucket() != ACTIVE_INDEX
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
&& !batchDelayExpired;
}
@@ -2766,7 +2768,7 @@
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
: Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS);
- } else if (job.getEffectivePriority() == JobInfo.PRIORITY_HIGH) {
+ } else if (job.getEffectivePriority() >= JobInfo.PRIORITY_HIGH) {
return mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
} else {
return mConstants.RUNTIME_MIN_GUARANTEE_MS;
@@ -3088,8 +3090,10 @@
return FREQUENT_INDEX;
} else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
return WORKING_INDEX;
- } else {
+ } else if (bucket > UsageStatsManager.STANDBY_BUCKET_EXEMPTED) {
return ACTIVE_INDEX;
+ } else {
+ return EXEMPTED_INDEX;
}
}
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/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0eea701..0456a9b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -17,6 +17,7 @@
package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
@@ -844,12 +845,15 @@
* exemptions.
*/
public int getEffectiveStandbyBucket() {
+ final int actualBucket = getStandbyBucket();
+ if (actualBucket == EXEMPTED_INDEX) {
+ return actualBucket;
+ }
if (uidActive || getJob().isExemptedFromAppStandby()) {
// Treat these cases as if they're in the ACTIVE bucket so that they get throttled
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
- final int actualBucket = getStandbyBucket();
if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
&& mHasMediaBackupExemption) {
// Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index dd5246a..c1728a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -21,6 +21,7 @@
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -132,6 +133,7 @@
*/
public long expirationTimeElapsed;
+ public long allowedTimePerPeriodMs;
public long windowSizeMs;
public int jobCountLimit;
public int sessionCountLimit;
@@ -213,6 +215,7 @@
@Override
public String toString() {
return "expirationTime=" + expirationTimeElapsed + ", "
+ + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", "
+ "windowSizeMs=" + windowSizeMs + ", "
+ "jobCountLimit=" + jobCountLimit + ", "
+ "sessionCountLimit=" + sessionCountLimit + ", "
@@ -236,6 +239,7 @@
if (obj instanceof ExecutionStats) {
ExecutionStats other = (ExecutionStats) obj;
return this.expirationTimeElapsed == other.expirationTimeElapsed
+ && this.allowedTimePerPeriodMs == other.allowedTimePerPeriodMs
&& this.windowSizeMs == other.windowSizeMs
&& this.jobCountLimit == other.jobCountLimit
&& this.sessionCountLimit == other.sessionCountLimit
@@ -261,6 +265,7 @@
public int hashCode() {
int result = 0;
result = 31 * result + hashLong(expirationTimeElapsed);
+ result = 31 * result + hashLong(allowedTimePerPeriodMs);
result = 31 * result + hashLong(windowSizeMs);
result = 31 * result + hashLong(jobCountLimit);
result = 31 * result + hashLong(sessionCountLimit);
@@ -350,7 +355,15 @@
private boolean mIsEnabled;
/** How much time each app will have to run jobs within their standby bucket window. */
- private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
+ private final long[] mAllowedTimePerPeriodMs = new long[]{
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS,
+ 0, // NEVER
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS
+ };
/**
* The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
@@ -365,12 +378,6 @@
private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
/**
- * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
- * when an app will have enough quota to transition from out-of-quota to in-quota.
- */
- private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-
- /**
* {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
* app will have enough quota to transition from out-of-quota to in-quota.
*/
@@ -450,7 +457,8 @@
QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
0, // NEVER
- QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS
+ QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS,
+ QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS
};
/** The maximum period any bucket can have. */
@@ -469,7 +477,8 @@
QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
QcConstants.DEFAULT_MAX_JOB_COUNT_RARE,
0, // NEVER
- QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED
+ QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED,
+ QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED
};
/**
@@ -487,6 +496,7 @@
QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE,
0, // NEVER
QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED,
+ QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED,
};
/**
@@ -506,7 +516,8 @@
QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS,
QcConstants.DEFAULT_EJ_LIMIT_RARE_MS,
0, // NEVER
- QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS
+ QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS,
+ QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS
};
private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS;
@@ -823,6 +834,11 @@
if (mService.isBatteryCharging()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
+ if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) {
+ return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2,
+ getTimeUntilEJQuotaConsumedLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()));
+ }
if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) {
return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2,
getTimeUntilEJQuotaConsumedLocked(
@@ -929,9 +945,11 @@
final long minSurplus;
if (priority <= JobInfo.PRIORITY_MIN) {
- minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin);
+ minSurplus = (long)
+ (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityMin);
} else if (priority <= JobInfo.PRIORITY_LOW) {
- minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow);
+ minSurplus = (long)
+ (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityLow);
} else {
minSurplus = 0;
}
@@ -989,7 +1007,7 @@
}
private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) {
- return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
+ return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs,
mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
}
@@ -1068,15 +1086,15 @@
if (sessions == null || sessions.size() == 0) {
// Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
// essentially run until they reach the maximum limit.
- if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
+ if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
return mMaxExecutionTimeMs;
}
- return mAllowedTimePerPeriodMs;
+ return mAllowedTimePerPeriodMs[standbyBucket];
}
final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
- final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority);
+ final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(standbyBucket, jobPriority);
final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
final long maxExecutionTimeRemainingMs =
mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
@@ -1087,7 +1105,7 @@
// Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
// essentially run until they reach the maximum limit.
- if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
+ if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
return calculateTimeUntilQuotaConsumedLocked(
sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
}
@@ -1103,14 +1121,19 @@
sessions, startWindowElapsed, allowedTimeRemainingMs));
}
- private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) {
+ private long getAllowedTimePerPeriodMs(int standbyBucket, @JobInfo.Priority int jobPriority) {
+ return getAllowedTimePerPeriodMs(mAllowedTimePerPeriodMs[standbyBucket], jobPriority);
+ }
+
+ private long getAllowedTimePerPeriodMs(long initialAllowedTime,
+ @JobInfo.Priority int jobPriority) {
if (jobPriority <= JobInfo.PRIORITY_MIN) {
- return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin));
+ return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityMin));
}
if (jobPriority <= JobInfo.PRIORITY_LOW) {
- return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow));
+ return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityLow));
}
- return mAllowedTimePerPeriodMs;
+ return initialAllowedTime;
}
/**
@@ -1237,16 +1260,19 @@
appStats[standbyBucket] = stats;
}
if (refreshStatsIfOld) {
+ final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket];
final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
Timer timer = mPkgTimers.get(userId, packageName);
if ((timer != null && timer.isActive())
|| stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
+ || stats.allowedTimePerPeriodMs != bucketAllowedTimeMs
|| stats.windowSizeMs != bucketWindowSizeMs
|| stats.jobCountLimit != jobCountLimit
|| stats.sessionCountLimit != sessionCountLimit) {
// The stats are no longer valid.
+ stats.allowedTimePerPeriodMs = bucketAllowedTimeMs;
stats.windowSizeMs = bucketWindowSizeMs;
stats.jobCountLimit = jobCountLimit;
stats.sessionCountLimit = sessionCountLimit;
@@ -1272,8 +1298,11 @@
} else {
stats.inQuotaTimeElapsed = 0;
}
- final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN);
- final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW);
+ final long allowedTimeMinMs =
+ getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_MIN);
+ final long allowedTimeLowMs =
+ getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_LOW);
+ final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs;
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -1287,9 +1316,9 @@
// If the timer is active, the value will be stale at the next method call, so
// invalidate now.
stats.expirationTimeElapsed = nowElapsed;
- if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+ if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) {
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
+ nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs);
}
if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
@@ -1346,9 +1375,9 @@
stats.executionTimeInWindowMs += session.endTimeElapsed - start;
stats.bgJobCountInWindow += session.bgJobCount;
- if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+ if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) {
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+ start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs
+ stats.windowSizeMs);
}
if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
@@ -1697,7 +1726,7 @@
if (js.setQuotaConstraintSatisfied(nowElapsed, true)) {
changedJobs.add(js);
}
- } else if (realStandbyBucket != ACTIVE_INDEX
+ } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
&& realStandbyBucket == js.getEffectiveStandbyBucket()
&& js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
@@ -1842,7 +1871,8 @@
}
}
final boolean inRegularQuota =
- stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+ stats.executionTimeInWindowMs
+ < getAllowedTimePerPeriodMs(standbyBucket, minPriority)
&& stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
&& isUnderJobCountQuota
&& isUnderTimingSessionCountQuota;
@@ -2921,9 +2951,29 @@
/** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
private static final String QC_CONSTANT_PREFIX = "qc_";
+ /**
+ * Previously used keys:
+ * * allowed_time_per_period_ms -- No longer used after splitting by bucket
+ */
+
@VisibleForTesting
- static final String KEY_ALLOWED_TIME_PER_PERIOD_MS =
- QC_CONSTANT_PREFIX + "allowed_time_per_period_ms";
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+ QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms";
@VisibleForTesting
static final String KEY_IN_QUOTA_BUFFER_MS =
QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
@@ -2934,6 +2984,9 @@
static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min";
@VisibleForTesting
+ static final String KEY_WINDOW_SIZE_EXEMPTED_MS =
+ QC_CONSTANT_PREFIX + "window_size_exempted_ms";
+ @VisibleForTesting
static final String KEY_WINDOW_SIZE_ACTIVE_MS =
QC_CONSTANT_PREFIX + "window_size_active_ms";
@VisibleForTesting
@@ -2952,6 +3005,9 @@
static final String KEY_MAX_EXECUTION_TIME_MS =
QC_CONSTANT_PREFIX + "max_execution_time_ms";
@VisibleForTesting
+ static final String KEY_MAX_JOB_COUNT_EXEMPTED =
+ QC_CONSTANT_PREFIX + "max_job_count_exempted";
+ @VisibleForTesting
static final String KEY_MAX_JOB_COUNT_ACTIVE =
QC_CONSTANT_PREFIX + "max_job_count_active";
@VisibleForTesting
@@ -2973,6 +3029,9 @@
static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window";
@VisibleForTesting
+ static final String KEY_MAX_SESSION_COUNT_EXEMPTED =
+ QC_CONSTANT_PREFIX + "max_session_count_exempted";
+ @VisibleForTesting
static final String KEY_MAX_SESSION_COUNT_ACTIVE =
QC_CONSTANT_PREFIX + "max_session_count_active";
@VisibleForTesting
@@ -2997,6 +3056,9 @@
static final String KEY_MIN_QUOTA_CHECK_DELAY_MS =
QC_CONSTANT_PREFIX + "min_quota_check_delay_ms";
@VisibleForTesting
+ static final String KEY_EJ_LIMIT_EXEMPTED_MS =
+ QC_CONSTANT_PREFIX + "ej_limit_exempted_ms";
+ @VisibleForTesting
static final String KEY_EJ_LIMIT_ACTIVE_MS =
QC_CONSTANT_PREFIX + "ej_limit_active_ms";
@VisibleForTesting
@@ -3039,14 +3101,26 @@
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
- private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ 10 * 60 * 1000L; // 10 minutes
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ 10 * 60 * 1000L; // 10 minutes
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+ 10 * 60 * 1000L; // 10 minutes
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+ 10 * 60 * 1000L; // 10 minutes
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS =
+ 10 * 60 * 1000L; // 10 minutes
+ private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f;
private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f;
+ private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS =
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time
private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
- DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time
private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
@@ -3060,8 +3134,9 @@
private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
MINUTE_IN_MILLIS;
private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
- private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
+ private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED =
75; // 75/window = 450/hr = 1/session
+ private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
(int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
@@ -3069,8 +3144,10 @@
private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
(int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
- private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
+ private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED =
75; // 450/hr
+ private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
+ DEFAULT_MAX_SESSION_COUNT_EXEMPTED;
private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
10; // 5/hr
private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
@@ -3081,6 +3158,7 @@
private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS;
+ private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
@@ -3096,8 +3174,39 @@
private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
- /** How much time each app will have to run jobs within their standby bucket window. */
- public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
+ /**
+ * How much time each app in the exempted bucket will have to run jobs within their standby
+ * bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS;
+ /**
+ * How much time each app in the active bucket will have to run jobs within their standby
+ * bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ /**
+ * How much time each app in the working set bucket will have to run jobs within their
+ * standby bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_WORKING_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS;
+ /**
+ * How much time each app in the frequent bucket will have to run jobs within their standby
+ * bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS;
+ /**
+ * How much time each app in the rare bucket will have to run jobs within their standby
+ * bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_RARE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS;
+ /**
+ * How much time each app in the restricted bucket will have to run jobs within their
+ * standby bucket window.
+ */
+ public long ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS;
/**
* How much time the package should have before transitioning from out-of-quota to in-quota.
@@ -3106,7 +3215,7 @@
public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
/**
- * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+ * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by
* {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
* surplus of this amount of remaining allowed time before we start running low priority
* jobs.
@@ -3114,7 +3223,7 @@
public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
/**
- * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+ * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by
* {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum
* surplus of this amount of remaining allowed time before we start running min priority
* jobs.
@@ -3123,35 +3232,42 @@
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past
+ * WINDOW_SIZE_MS.
+ */
+ public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS;
+
+ /**
+ * The quota window size of the particular standby bucket. Apps in this standby bucket are
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RARE_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
- * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
+ * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS} within the past
* WINDOW_SIZE_MS.
*/
public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS;
@@ -3165,6 +3281,12 @@
* The maximum number of jobs an app can run within this particular standby bucket's
* window size.
*/
+ public int MAX_JOB_COUNT_EXEMPTED = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
+
+ /**
+ * The maximum number of jobs an app can run within this particular standby bucket's
+ * window size.
+ */
public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE;
/**
@@ -3204,6 +3326,12 @@
* The maximum number of {@link TimingSession TimingSessions} an app can run within this
* particular standby bucket's window size.
*/
+ public int MAX_SESSION_COUNT_EXEMPTED = DEFAULT_MAX_SESSION_COUNT_EXEMPTED;
+
+ /**
+ * The maximum number of {@link TimingSession TimingSessions} an app can run within this
+ * particular standby bucket's window size.
+ */
public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE;
/**
@@ -3232,7 +3360,7 @@
/**
* The maximum number of {@link TimingSession TimingSessions} that can run within the past
- * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
+ * {@link #RATE_LIMITING_WINDOW_MS}.
*/
public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -3275,6 +3403,13 @@
* standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
* in any rewards or free EJs).
*/
+ public long EJ_LIMIT_EXEMPTED_MS = DEFAULT_EJ_LIMIT_EXEMPTED_MS;
+
+ /**
+ * The total expedited job session limit of the particular standby bucket. Apps in this
+ * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
+ * in any rewards or free EJs).
+ */
public long EJ_LIMIT_ACTIVE_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
/**
@@ -3358,7 +3493,12 @@
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
switch (key) {
- case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS:
+ case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS:
case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW:
case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN:
case KEY_IN_QUOTA_BUFFER_MS:
@@ -3388,6 +3528,15 @@
updateEJLimitConstantsLocked();
break;
+ case KEY_MAX_JOB_COUNT_EXEMPTED:
+ MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED);
+ int newExemptedMaxJobCount =
+ Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_EXEMPTED);
+ if (mMaxBucketJobCounts[EXEMPTED_INDEX] != newExemptedMaxJobCount) {
+ mMaxBucketJobCounts[EXEMPTED_INDEX] = newExemptedMaxJobCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
case KEY_MAX_JOB_COUNT_ACTIVE:
MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE);
int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
@@ -3432,6 +3581,16 @@
mShouldReevaluateConstraints = true;
}
break;
+ case KEY_MAX_SESSION_COUNT_EXEMPTED:
+ MAX_SESSION_COUNT_EXEMPTED =
+ properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_EXEMPTED);
+ int newExemptedMaxSessionCount =
+ Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_EXEMPTED);
+ if (mMaxBucketSessionCounts[EXEMPTED_INDEX] != newExemptedMaxSessionCount) {
+ mMaxBucketSessionCounts[EXEMPTED_INDEX] = newExemptedMaxSessionCount;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
case KEY_MAX_SESSION_COUNT_ACTIVE:
MAX_SESSION_COUNT_ACTIVE =
properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
@@ -3579,15 +3738,34 @@
// Query the values as an atomic set.
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS,
+ KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ KEY_IN_QUOTA_BUFFER_MS,
KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
- KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
+ KEY_MAX_EXECUTION_TIME_MS,
+ KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
KEY_WINDOW_SIZE_WORKING_MS,
KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
KEY_WINDOW_SIZE_RESTRICTED_MS);
- ALLOWED_TIME_PER_PERIOD_MS =
- properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
- DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+ ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS);
+ ALLOWED_TIME_PER_PERIOD_ACTIVE_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS);
+ ALLOWED_TIME_PER_PERIOD_WORKING_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS);
+ ALLOWED_TIME_PER_PERIOD_FREQUENT_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS);
+ ALLOWED_TIME_PER_PERIOD_RARE_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS);
+ ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
+ properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS);
ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW,
DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW);
@@ -3598,6 +3776,8 @@
DEFAULT_IN_QUOTA_BUFFER_MS);
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
DEFAULT_MAX_EXECUTION_TIME_MS);
+ WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
+ DEFAULT_WINDOW_SIZE_EXEMPTED_MS);
WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
DEFAULT_WINDOW_SIZE_ACTIVE_MS);
WINDOW_SIZE_WORKING_MS =
@@ -3618,20 +3798,55 @@
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
mShouldReevaluateConstraints = true;
}
- long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
- Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
- if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
- mAllowedTimePerPeriodMs = newAllowedTimeMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+ long minAllowedTimeMs = Long.MAX_VALUE;
+ long newAllowedTimeExemptedMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeExemptedMs);
+ if (mAllowedTimePerPeriodMs[EXEMPTED_INDEX] != newAllowedTimeExemptedMs) {
+ mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = newAllowedTimeExemptedMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeActiveMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeActiveMs);
+ if (mAllowedTimePerPeriodMs[ACTIVE_INDEX] != newAllowedTimeActiveMs) {
+ mAllowedTimePerPeriodMs[ACTIVE_INDEX] = newAllowedTimeActiveMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeWorkingMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_WORKING_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeWorkingMs);
+ if (mAllowedTimePerPeriodMs[WORKING_INDEX] != newAllowedTimeWorkingMs) {
+ mAllowedTimePerPeriodMs[WORKING_INDEX] = newAllowedTimeWorkingMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeFrequentMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeFrequentMs);
+ if (mAllowedTimePerPeriodMs[FREQUENT_INDEX] != newAllowedTimeFrequentMs) {
+ mAllowedTimePerPeriodMs[FREQUENT_INDEX] = newAllowedTimeFrequentMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeRareMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RARE_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRareMs);
+ if (mAllowedTimePerPeriodMs[RARE_INDEX] != newAllowedTimeRareMs) {
+ mAllowedTimePerPeriodMs[RARE_INDEX] = newAllowedTimeRareMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newAllowedTimeRestrictedMs = Math.min(mMaxExecutionTimeMs,
+ Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS));
+ minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRestrictedMs);
+ if (mAllowedTimePerPeriodMs[RESTRICTED_INDEX] != newAllowedTimeRestrictedMs) {
+ mAllowedTimePerPeriodMs[RESTRICTED_INDEX] = newAllowedTimeRestrictedMs;
mShouldReevaluateConstraints = true;
}
// Make sure quota buffer is non-negative, not greater than allowed time per period,
// and no more than 5 minutes.
- long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
+ long newQuotaBufferMs = Math.max(0, Math.min(minAllowedTimeMs,
Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
if (mQuotaBufferMs != newQuotaBufferMs) {
mQuotaBufferMs = newQuotaBufferMs;
- mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
mShouldReevaluateConstraints = true;
}
@@ -3652,32 +3867,38 @@
mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin;
mShouldReevaluateConstraints = true;
}
- long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ long newExemptedPeriodMs = Math.max(mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
+ if (mBucketPeriodsMs[EXEMPTED_INDEX] != newExemptedPeriodMs) {
+ mBucketPeriodsMs[EXEMPTED_INDEX] = newExemptedPeriodMs;
+ mShouldReevaluateConstraints = true;
+ }
+ long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs[ACTIVE_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
mShouldReevaluateConstraints = true;
}
- long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs[WORKING_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
mShouldReevaluateConstraints = true;
}
- long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs[FREQUENT_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
mShouldReevaluateConstraints = true;
}
- long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs[RARE_INDEX],
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
mShouldReevaluateConstraints = true;
}
// Fit in the range [allowed time (10 mins), 1 week].
- long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs,
+ long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs[RESTRICTED_INDEX],
Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS));
if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) {
mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
@@ -3740,11 +3961,14 @@
// Query the values as an atomic set.
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EJ_LIMIT_EXEMPTED_MS,
KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS,
KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS,
KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS,
KEY_EJ_LIMIT_ADDITION_INSTALLER_MS,
KEY_EJ_WINDOW_SIZE_MS);
+ EJ_LIMIT_EXEMPTED_MS = properties.getLong(
+ KEY_EJ_LIMIT_EXEMPTED_MS, DEFAULT_EJ_LIMIT_EXEMPTED_MS);
EJ_LIMIT_ACTIVE_MS = properties.getLong(
KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
EJ_LIMIT_WORKING_MS = properties.getLong(
@@ -3770,8 +3994,15 @@
mShouldReevaluateConstraints = true;
}
// The limit must be in the range [15 minutes, window size].
+ long newExemptLimitMs = Math.max(15 * MINUTE_IN_MILLIS,
+ Math.min(newWindowSizeMs, EJ_LIMIT_EXEMPTED_MS));
+ if (mEJLimitsMs[EXEMPTED_INDEX] != newExemptLimitMs) {
+ mEJLimitsMs[EXEMPTED_INDEX] = newExemptLimitMs;
+ mShouldReevaluateConstraints = true;
+ }
+ // The limit must be in the range [15 minutes, exempted limit].
long newActiveLimitMs = Math.max(15 * MINUTE_IN_MILLIS,
- Math.min(newWindowSizeMs, EJ_LIMIT_ACTIVE_MS));
+ Math.min(newExemptLimitMs, EJ_LIMIT_ACTIVE_MS));
if (mEJLimitsMs[ACTIVE_INDEX] != newActiveLimitMs) {
mEJLimitsMs[ACTIVE_INDEX] = newActiveLimitMs;
mShouldReevaluateConstraints = true;
@@ -3823,18 +4054,31 @@
pw.println();
pw.println("QuotaController:");
pw.increaseIndent();
- pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, ALLOWED_TIME_PER_PERIOD_WORKING_MS)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, ALLOWED_TIME_PER_PERIOD_RARE_MS)
+ .println();
+ pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println();
pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW)
.println();
pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN)
.println();
pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
+ pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println();
pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
pw.print(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
pw.print(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
pw.print(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println();
pw.print(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
+ pw.print(KEY_MAX_JOB_COUNT_EXEMPTED, MAX_JOB_COUNT_EXEMPTED).println();
pw.print(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
pw.print(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
pw.print(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
@@ -3843,6 +4087,7 @@
pw.print(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
pw.print(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
+ pw.print(KEY_MAX_SESSION_COUNT_EXEMPTED, MAX_SESSION_COUNT_EXEMPTED).println();
pw.print(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
pw.print(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
pw.print(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
@@ -3854,6 +4099,7 @@
TIMING_SESSION_COALESCING_DURATION_MS).println();
pw.print(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println();
+ pw.print(KEY_EJ_LIMIT_EXEMPTED_MS, EJ_LIMIT_EXEMPTED_MS).println();
pw.print(KEY_EJ_LIMIT_ACTIVE_MS, EJ_LIMIT_ACTIVE_MS).println();
pw.print(KEY_EJ_LIMIT_WORKING_MS, EJ_LIMIT_WORKING_MS).println();
pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println();
@@ -3875,8 +4121,6 @@
private void dump(ProtoOutputStream proto) {
final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
- proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
- ALLOWED_TIME_PER_PERIOD_MS);
proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS);
proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS,
WINDOW_SIZE_ACTIVE_MS);
@@ -3946,7 +4190,7 @@
//////////////////////// TESTING HELPERS /////////////////////////////
@VisibleForTesting
- long getAllowedTimePerPeriodMs() {
+ long[] getAllowedTimePerPeriodMs() {
return mAllowedTimePerPeriodMs;
}
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..b843dca 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;
@@ -347,6 +347,22 @@
*/
boolean mLinkCrossProfileApps =
ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS;
+
+ /**
+ * Duration (in millis) for the window where events occurring will be considered as
+ * broadcast response, starting from the point when an app receives a broadcast.
+ */
+ volatile long mBroadcastResponseWindowDurationMillis =
+ ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS;
+
+ /**
+ * Process state threshold that is used for deciding whether or not an app is in the background
+ * in the context of recording broadcast response stats. Apps whose process state is higher
+ * than this threshold state will be considered to be in background.
+ */
+ volatile int mBroadcastResponseFgThresholdState =
+ ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE;
+
/**
* Whether we should allow apps into the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
@@ -1406,13 +1422,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");
@@ -1774,6 +1790,15 @@
}
}
+ @Override
+ public long getBroadcastResponseWindowDurationMs() {
+ return mBroadcastResponseWindowDurationMillis;
+ }
+
+ @Override
+ public int getBroadcastResponseFgThresholdState() {
+ return mBroadcastResponseFgThresholdState;
+ }
@Override
public void flushToDisk() {
@@ -1799,27 +1824,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);
@@ -2033,6 +2067,14 @@
TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
pw.println();
+ pw.print(" mBroadcastResponseWindowDurationMillis=");
+ TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw);
+ pw.println();
+
+ pw.print(" mBroadcastResponseFgThresholdState=");
+ pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState));
+ pw.println();
+
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
pw.print(" mAllowRestrictedBucket=");
@@ -2464,6 +2506,10 @@
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare",
KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted"
};
+ private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ "broadcast_response_window_timeout_ms";
+ private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+ "broadcast_response_fg_threshold_state";
public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
@@ -2493,6 +2539,10 @@
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
+ public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
+ 2 * ONE_MINUTE;
+ public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE =
+ ActivityManager.PROCESS_STATE_TOP;
ConstantsObserver(Handler handler) {
super(handler);
@@ -2610,6 +2660,16 @@
KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION,
DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT);
break;
+ case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS:
+ mBroadcastResponseWindowDurationMillis = properties.getLong(
+ KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS,
+ DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS);
+ break;
+ case KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE:
+ mBroadcastResponseFgThresholdState = properties.getInt(
+ KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE,
+ DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE);
+ break;
default:
if (!timeThresholdsUpdated
&& (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD)
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index c5dc51c..e407e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -5,7 +5,9 @@
"options": [
{"include-filter": "android.app.usage.cts.UsageStatsTest"},
{"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.MediumTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"}
]
},
{
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index d963e68..2c2af28 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -61,10 +61,13 @@
"test_com.android.media",
],
min_sdk_version: "29",
+ lint: {
+ strict_updatability_linting: true,
+ },
visibility: [
"//frameworks/av/apex:__subpackages__",
- "//frameworks/base", // For framework-all
"//frameworks/base/apex/media/service",
+ "//frameworks/base/api", // For framework-all
],
}
diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml
index e1b1450..95eea45 100644
--- a/apex/media/framework/lint-baseline.xml
+++ b/apex/media/framework/lint-baseline.xml
@@ -1,312 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
- errorLine1=" new ApplicationMediaCapabilities.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ id="DefaultLocale"
+ message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
+ errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {"
+ errorLine2=" ~~~~~~~~~~~">
<location
file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
- line="208"
- column="29"/>
+ line="121"
+ column="57"/>
</issue>
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`"
- errorLine1=" ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ id="DefaultLocale"
+ message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
+ errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}","
+ errorLine2=" ^">
<location
- file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
- line="314"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`"
- errorLine1=" e.rethrowFromSystemServer();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java"
- line="110"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`"
- errorLine1=" dest.writeParcelableCreator((Parcelable) parcelable);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
- line="77"
- column="14"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`"
- errorLine1=" return from.readParcelableCreator(loader);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java"
- line="82"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`"
- errorLine1=" this.mediaFormat = mediaFormat;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="273"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`"
- errorLine1=" this.drmInitData = drmInitData;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="274"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" this.timeMicros = timeMicros;"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="295"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" this.position = position;"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="296"
- column="13"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="302"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="302"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`"
- errorLine1=" SeekPoint other = (SeekPoint) obj;"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="313"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
+ file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java"
+ line="1651"
column="20"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" return timeMicros == other.timeMicros && position == other.position;"
- errorLine2=" ~~~~~~~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+ errorLine1=" Bundle out = parcel.readBundle(null);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="314"
- column="34"/>
+ file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java"
+ line="303"
+ column="33"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" int result = (int) timeMicros;"
- errorLine2=" ~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+ errorLine1=" mCustomExtras = in.readBundle();"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="319"
- column="32"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java"
+ line="104"
+ column="28"/>
</issue>
<issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" result = 31 * result + (int) position;"
- errorLine2=" ~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
+ errorLine1=" mSessionLink = in.readParcelable(null);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="320"
- column="42"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+ line="141"
+ column="27"/>
</issue>
<issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" public interface SeekableInputReader extends InputReader {"
- errorLine2=" ~~~~~~~~~~~">
+ id="ParcelClassLoader"
+ message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
+ errorLine1=" Bundle extras = in.readBundle();"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="352"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`"
- errorLine1=" @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1071"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)"
- errorLine1=" mExoDataReader.mInputReader = seekableInputReader;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1201"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" mPendingSeekPosition = seekPoint.position;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1287"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" mPendingSeekTimeMicros = seekPoint.timeMicros;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1288"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`"
- errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1290"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`"
- errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1290"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`"
- errorLine1=" if (schemeInitData.uuid.equals(schemeUuid)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1579"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" private static final class DataReaderAdapter implements InputReader {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1872"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`"
- errorLine1=" private static final class ParsableByteArrayAdapter implements InputReader {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java"
- line="1905"
- column="68"/>
+ file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
+ line="144"
+ column="28"/>
</issue>
</issues>
diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp
index cf384ac..834e5cb 100644
--- a/apex/media/service/Android.bp
+++ b/apex/media/service/Android.bp
@@ -39,6 +39,7 @@
":service-media-s-sources",
],
libs: [
+ "androidx.annotation_annotation",
"updatable-media",
"modules-annotation-minsdk",
"modules-utils-build",
@@ -46,6 +47,9 @@
jarjar_rules: "jarjar_rules.txt",
sdk_version: "system_server_current",
min_sdk_version: "29", // TODO: We may need to bump this at some point.
+ lint: {
+ strict_updatability_linting: true,
+ },
apex_available: [
"com.android.media",
],
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
index 7d47e25..4223fa6 100644
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -46,6 +46,8 @@
import android.util.SparseIntArray;
import android.view.KeyEvent;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
import com.android.modules.annotation.MinSdk;
import com.android.server.SystemService;
@@ -63,6 +65,7 @@
* @hide
*/
@MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
public class MediaCommunicationService extends SystemService {
private static final String TAG = "MediaCommunicationSrv";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
index 47b14b6..8145861 100644
--- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java
+++ b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
@@ -18,9 +18,13 @@
import android.annotation.Nullable;
import android.media.Session2Token;
+import android.os.Build;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.annotation.MinSdk;
import com.android.server.media.MediaCommunicationService.Session2Record;
import java.util.ArrayList;
@@ -33,6 +37,8 @@
* Higher priority session has more chance to be selected as media button session,
* which receives the media button events.
*/
+@MinSdk(Build.VERSION_CODES.S)
+@RequiresApi(Build.VERSION_CODES.S)
class SessionPriorityList {
private static final String TAG = "SessionPriorityList";
private final Object mLock = new Object();
diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml
index 05ce17c..def6baf 100644
--- a/apex/media/service/lint-baseline.xml
+++ b/apex/media/service/lint-baseline.xml
@@ -1,37 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="242"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="243"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level S (current min is 29): `MediaParceledListSlice`"
- errorLine1=" MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java"
- line="386"
- column="60"/>
- </issue>
+<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
</issues>
diff --git a/api/Android.bp b/api/Android.bp
index d8727f9..d57f5db 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -116,7 +116,6 @@
"framework-graphics",
"framework-media",
"framework-mediaprovider",
- "framework-nearby",
"framework-permission",
"framework-permission-s",
"framework-scheduling",
@@ -129,6 +128,7 @@
"i18n.module.public.api",
],
conditional_bootclasspath: [
+ "framework-auxiliary",
"framework-supplementalapi",
],
system_server_classpath: [
diff --git a/api/api.go b/api/api.go
index aa9e399..5e5f60e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,7 +27,7 @@
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
-var modules_with_only_public_scope = []string{i18n, conscrypt}
+var core_libraries_modules = []string{art, conscrypt, i18n}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -149,8 +149,6 @@
// This produces the same annotations.zip as framework-doc-stubs, but by using
// outputs from individual modules instead of all the source code.
func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
- // Conscrypt and i18n currently do not enable annotations
- modules = removeAll(modules, []string{conscrypt, i18n})
props := genruleProps{}
props.Name = proptools.StringPtr("sdk-annotations.zip")
props.Tools = []string{"merge_annotation_zips", "soong_zip"}
@@ -195,19 +193,35 @@
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
props := libraryProps{}
- modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope)
props.Name = proptools.StringPtr("all-modules-system-stubs")
- props.Static_libs = append(
- transformArray(modules_with_only_public_scope, "", ".stubs"),
- transformArray(modules_with_system_stubs, "", ".stubs.system")...)
+ props.Static_libs = transformArray(modules, "", ".stubs.system")
props.Sdk_version = proptools.StringPtr("module_current")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(java.LibraryFactory, &props)
}
-func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) {
+func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
+ // This module is for the "framework-all" module, which should not include the core libraries.
+ modules = removeAll(modules, core_libraries_modules)
+ // TODO(b/214988855): remove the line below when framework-bluetooth has an impl jar.
+ modules = remove(modules, "framework-bluetooth")
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-framework-module-impl")
+ props.Static_libs = transformArray(modules, "", ".impl")
+ // Media module's impl jar is called "updatable-media"
+ for i, v := range props.Static_libs {
+ if v == "framework-media.impl" {
+ props.Static_libs[i] = "updatable-media"
+ }
+ }
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+}
+
+func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
// The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes.
- modules = removeAll(modules, []string{art, conscrypt, i18n})
+ modules = removeAll(modules, core_libraries_modules)
props := libraryProps{}
props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api")
props.Static_libs = transformArray(modules, "", ".stubs.module_lib")
@@ -226,8 +240,6 @@
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
var textFiles []MergedTxtDefinition
- // Two module libraries currently do not support @SystemApi so only have the public scope.
- bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope)
tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
for i, f := range []string{"current.txt", "removed.txt"} {
@@ -241,14 +253,14 @@
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-system-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.system" + tagSuffix[i],
Scope: "system",
})
textFiles = append(textFiles, MergedTxtDefinition{
TxtFilename: f,
BaseTxt: ":non-updatable-module-lib-" + f,
- Modules: bcpWithSystemApi,
+ Modules: bootclasspath,
ModuleTag: "{.module-lib" + tagSuffix[i],
Scope: "module-lib",
})
@@ -277,7 +289,8 @@
createMergedPublicStubs(ctx, bootclasspath)
createMergedSystemStubs(ctx, bootclasspath)
- createMergedModuleLibStubs(ctx, bootclasspath)
+ createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
+ createMergedFrameworkImpl(ctx, bootclasspath)
createMergedAnnotations(ctx, bootclasspath)
diff --git a/boot/Android.bp b/boot/Android.bp
index 3273f2c..55ffe7c 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -56,6 +56,10 @@
module: "art-bootclasspath-fragment",
},
{
+ apex: "com.android.auxiliary",
+ module: "com.android.auxiliary-bootclasspath-fragment",
+ },
+ {
apex: "com.android.conscrypt",
module: "com.android.conscrypt-bootclasspath-fragment",
},
@@ -76,10 +80,6 @@
module: "com.android.mediaprovider-bootclasspath-fragment",
},
{
- apex: "com.android.nearby",
- module: "com.android.nearby-bootclasspath-fragment",
- },
- {
apex: "com.android.os.statsd",
module: "com.android.os.statsd-bootclasspath-fragment",
},
diff --git a/boot/hiddenapi/hiddenapi-max-target-o.txt b/boot/hiddenapi/hiddenapi-max-target-o.txt
index 9153426..50e0a1b 100644
--- a/boot/hiddenapi/hiddenapi-max-target-o.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-o.txt
@@ -35446,51 +35446,6 @@
Landroid/net/IIpConnectivityMetrics;->addNetdEventCallback(ILandroid/net/INetdEventCallback;)Z
Landroid/net/IIpConnectivityMetrics;->logEvent(Landroid/net/ConnectivityMetricsEvent;)I
Landroid/net/IIpConnectivityMetrics;->removeNetdEventCallback(I)Z
-Landroid/net/IIpSecService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/net/IIpSecService$Stub$Proxy;->addAddressToTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
-Landroid/net/IIpSecService$Stub$Proxy;->allocateSecurityParameterIndex(Ljava/lang/String;ILandroid/os/IBinder;)Landroid/net/IpSecSpiResponse;
-Landroid/net/IIpSecService$Stub$Proxy;->applyTransportModeTransform(Landroid/os/ParcelFileDescriptor;II)V
-Landroid/net/IIpSecService$Stub$Proxy;->applyTunnelModeTransform(IIILjava/lang/String;)V
-Landroid/net/IIpSecService$Stub$Proxy;->closeUdpEncapsulationSocket(I)V
-Landroid/net/IIpSecService$Stub$Proxy;->createTransform(Landroid/net/IpSecConfig;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTransformResponse;
-Landroid/net/IIpSecService$Stub$Proxy;->createTunnelInterface(Ljava/lang/String;Ljava/lang/String;Landroid/net/Network;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTunnelInterfaceResponse;
-Landroid/net/IIpSecService$Stub$Proxy;->deleteTransform(I)V
-Landroid/net/IIpSecService$Stub$Proxy;->deleteTunnelInterface(ILjava/lang/String;)V
-Landroid/net/IIpSecService$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/net/IIpSecService$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/net/IIpSecService$Stub$Proxy;->openUdpEncapsulationSocket(ILandroid/os/IBinder;)Landroid/net/IpSecUdpEncapResponse;
-Landroid/net/IIpSecService$Stub$Proxy;->releaseSecurityParameterIndex(I)V
-Landroid/net/IIpSecService$Stub$Proxy;->removeAddressFromTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
-Landroid/net/IIpSecService$Stub$Proxy;->removeTransportModeTransforms(Landroid/os/ParcelFileDescriptor;)V
-Landroid/net/IIpSecService$Stub;-><init>()V
-Landroid/net/IIpSecService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IIpSecService;
-Landroid/net/IIpSecService$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/net/IIpSecService$Stub;->TRANSACTION_addAddressToTunnelInterface:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_allocateSecurityParameterIndex:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_applyTransportModeTransform:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_applyTunnelModeTransform:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_closeUdpEncapsulationSocket:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_createTransform:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_createTunnelInterface:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_deleteTransform:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_deleteTunnelInterface:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_openUdpEncapsulationSocket:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_releaseSecurityParameterIndex:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_removeAddressFromTunnelInterface:I
-Landroid/net/IIpSecService$Stub;->TRANSACTION_removeTransportModeTransforms:I
-Landroid/net/IIpSecService;->addAddressToTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
-Landroid/net/IIpSecService;->allocateSecurityParameterIndex(Ljava/lang/String;ILandroid/os/IBinder;)Landroid/net/IpSecSpiResponse;
-Landroid/net/IIpSecService;->applyTransportModeTransform(Landroid/os/ParcelFileDescriptor;II)V
-Landroid/net/IIpSecService;->applyTunnelModeTransform(IIILjava/lang/String;)V
-Landroid/net/IIpSecService;->closeUdpEncapsulationSocket(I)V
-Landroid/net/IIpSecService;->createTransform(Landroid/net/IpSecConfig;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTransformResponse;
-Landroid/net/IIpSecService;->createTunnelInterface(Ljava/lang/String;Ljava/lang/String;Landroid/net/Network;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTunnelInterfaceResponse;
-Landroid/net/IIpSecService;->deleteTransform(I)V
-Landroid/net/IIpSecService;->deleteTunnelInterface(ILjava/lang/String;)V
-Landroid/net/IIpSecService;->openUdpEncapsulationSocket(ILandroid/os/IBinder;)Landroid/net/IpSecUdpEncapResponse;
-Landroid/net/IIpSecService;->releaseSecurityParameterIndex(I)V
-Landroid/net/IIpSecService;->removeAddressFromTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
-Landroid/net/IIpSecService;->removeTransportModeTransforms(Landroid/os/ParcelFileDescriptor;)V
Landroid/net/INetd$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/net/INetd$Stub$Proxy;->addVirtualTunnelInterface(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V
Landroid/net/INetd$Stub$Proxy;->bandwidthEnableDataSaver(Z)Z
@@ -35914,174 +35869,6 @@
Landroid/net/InterfaceConfiguration;->mHwAddr:Ljava/lang/String;
Landroid/net/InterfaceConfiguration;->setHardwareAddress(Ljava/lang/String;)V
Landroid/net/InterfaceConfiguration;->validateFlag(Ljava/lang/String;)V
-Landroid/net/IpSecAlgorithm;->checkValidOrThrow(Ljava/lang/String;II)V
-Landroid/net/IpSecAlgorithm;->CRYPT_NULL:Ljava/lang/String;
-Landroid/net/IpSecAlgorithm;->equals(Landroid/net/IpSecAlgorithm;Landroid/net/IpSecAlgorithm;)Z
-Landroid/net/IpSecAlgorithm;->isAead()Z
-Landroid/net/IpSecAlgorithm;->isAuthentication()Z
-Landroid/net/IpSecAlgorithm;->isEncryption()Z
-Landroid/net/IpSecAlgorithm;->isUnsafeBuild()Z
-Landroid/net/IpSecAlgorithm;->mKey:[B
-Landroid/net/IpSecAlgorithm;->mName:Ljava/lang/String;
-Landroid/net/IpSecAlgorithm;->mTruncLenBits:I
-Landroid/net/IpSecAlgorithm;->TAG:Ljava/lang/String;
-Landroid/net/IpSecConfig;-><init>()V
-Landroid/net/IpSecConfig;-><init>(Landroid/net/IpSecConfig;)V
-Landroid/net/IpSecConfig;-><init>(Landroid/os/Parcel;)V
-Landroid/net/IpSecConfig;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/IpSecConfig;->equals(Landroid/net/IpSecConfig;Landroid/net/IpSecConfig;)Z
-Landroid/net/IpSecConfig;->getAuthenticatedEncryption()Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->getAuthentication()Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->getDestinationAddress()Ljava/lang/String;
-Landroid/net/IpSecConfig;->getEncapRemotePort()I
-Landroid/net/IpSecConfig;->getEncapSocketResourceId()I
-Landroid/net/IpSecConfig;->getEncapType()I
-Landroid/net/IpSecConfig;->getEncryption()Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->getMarkMask()I
-Landroid/net/IpSecConfig;->getMarkValue()I
-Landroid/net/IpSecConfig;->getMode()I
-Landroid/net/IpSecConfig;->getNattKeepaliveInterval()I
-Landroid/net/IpSecConfig;->getNetwork()Landroid/net/Network;
-Landroid/net/IpSecConfig;->getSourceAddress()Ljava/lang/String;
-Landroid/net/IpSecConfig;->getSpiResourceId()I
-Landroid/net/IpSecConfig;->mAuthenticatedEncryption:Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->mAuthentication:Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->mDestinationAddress:Ljava/lang/String;
-Landroid/net/IpSecConfig;->mEncapRemotePort:I
-Landroid/net/IpSecConfig;->mEncapSocketResourceId:I
-Landroid/net/IpSecConfig;->mEncapType:I
-Landroid/net/IpSecConfig;->mEncryption:Landroid/net/IpSecAlgorithm;
-Landroid/net/IpSecConfig;->mMarkMask:I
-Landroid/net/IpSecConfig;->mMarkValue:I
-Landroid/net/IpSecConfig;->mMode:I
-Landroid/net/IpSecConfig;->mNattKeepaliveInterval:I
-Landroid/net/IpSecConfig;->mNetwork:Landroid/net/Network;
-Landroid/net/IpSecConfig;->mSourceAddress:Ljava/lang/String;
-Landroid/net/IpSecConfig;->mSpiResourceId:I
-Landroid/net/IpSecConfig;->setAuthenticatedEncryption(Landroid/net/IpSecAlgorithm;)V
-Landroid/net/IpSecConfig;->setAuthentication(Landroid/net/IpSecAlgorithm;)V
-Landroid/net/IpSecConfig;->setDestinationAddress(Ljava/lang/String;)V
-Landroid/net/IpSecConfig;->setEncapRemotePort(I)V
-Landroid/net/IpSecConfig;->setEncapSocketResourceId(I)V
-Landroid/net/IpSecConfig;->setEncapType(I)V
-Landroid/net/IpSecConfig;->setEncryption(Landroid/net/IpSecAlgorithm;)V
-Landroid/net/IpSecConfig;->setMarkMask(I)V
-Landroid/net/IpSecConfig;->setMarkValue(I)V
-Landroid/net/IpSecConfig;->setMode(I)V
-Landroid/net/IpSecConfig;->setNattKeepaliveInterval(I)V
-Landroid/net/IpSecConfig;->setNetwork(Landroid/net/Network;)V
-Landroid/net/IpSecConfig;->setSourceAddress(Ljava/lang/String;)V
-Landroid/net/IpSecConfig;->setSpiResourceId(I)V
-Landroid/net/IpSecConfig;->TAG:Ljava/lang/String;
-Landroid/net/IpSecManager$IpSecTunnelInterface;-><init>(Landroid/content/Context;Landroid/net/IIpSecService;Ljava/net/InetAddress;Ljava/net/InetAddress;Landroid/net/Network;)V
-Landroid/net/IpSecManager$IpSecTunnelInterface;->addAddress(Ljava/net/InetAddress;I)V
-Landroid/net/IpSecManager$IpSecTunnelInterface;->getInterfaceName()Ljava/lang/String;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->getResourceId()I
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mCloseGuard:Ldalvik/system/CloseGuard;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mInterfaceName:Ljava/lang/String;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mLocalAddress:Ljava/net/InetAddress;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mOpPackageName:Ljava/lang/String;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mRemoteAddress:Ljava/net/InetAddress;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mResourceId:I
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mService:Landroid/net/IIpSecService;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->mUnderlyingNetwork:Landroid/net/Network;
-Landroid/net/IpSecManager$IpSecTunnelInterface;->removeAddress(Ljava/net/InetAddress;I)V
-Landroid/net/IpSecManager$ResourceUnavailableException;-><init>(Ljava/lang/String;)V
-Landroid/net/IpSecManager$SecurityParameterIndex;-><init>(Landroid/net/IIpSecService;Ljava/net/InetAddress;I)V
-Landroid/net/IpSecManager$SecurityParameterIndex;->getResourceId()I
-Landroid/net/IpSecManager$SecurityParameterIndex;->mCloseGuard:Ldalvik/system/CloseGuard;
-Landroid/net/IpSecManager$SecurityParameterIndex;->mDestinationAddress:Ljava/net/InetAddress;
-Landroid/net/IpSecManager$SecurityParameterIndex;->mResourceId:I
-Landroid/net/IpSecManager$SecurityParameterIndex;->mService:Landroid/net/IIpSecService;
-Landroid/net/IpSecManager$SecurityParameterIndex;->mSpi:I
-Landroid/net/IpSecManager$SpiUnavailableException;-><init>(Ljava/lang/String;I)V
-Landroid/net/IpSecManager$SpiUnavailableException;->mSpi:I
-Landroid/net/IpSecManager$Status;->OK:I
-Landroid/net/IpSecManager$Status;->RESOURCE_UNAVAILABLE:I
-Landroid/net/IpSecManager$Status;->SPI_UNAVAILABLE:I
-Landroid/net/IpSecManager$UdpEncapsulationSocket;-><init>(Landroid/net/IIpSecService;I)V
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->getResourceId()I
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->mCloseGuard:Ldalvik/system/CloseGuard;
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->mPfd:Landroid/os/ParcelFileDescriptor;
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->mPort:I
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->mResourceId:I
-Landroid/net/IpSecManager$UdpEncapsulationSocket;->mService:Landroid/net/IIpSecService;
-Landroid/net/IpSecManager;-><init>(Landroid/content/Context;Landroid/net/IIpSecService;)V
-Landroid/net/IpSecManager;->applyTunnelModeTransform(Landroid/net/IpSecManager$IpSecTunnelInterface;ILandroid/net/IpSecTransform;)V
-Landroid/net/IpSecManager;->createIpSecTunnelInterface(Ljava/net/InetAddress;Ljava/net/InetAddress;Landroid/net/Network;)Landroid/net/IpSecManager$IpSecTunnelInterface;
-Landroid/net/IpSecManager;->INVALID_RESOURCE_ID:I
-Landroid/net/IpSecManager;->maybeHandleServiceSpecificException(Landroid/os/ServiceSpecificException;)V
-Landroid/net/IpSecManager;->mContext:Landroid/content/Context;
-Landroid/net/IpSecManager;->mService:Landroid/net/IIpSecService;
-Landroid/net/IpSecManager;->removeTunnelModeTransform(Landroid/net/Network;Landroid/net/IpSecTransform;)V
-Landroid/net/IpSecManager;->rethrowCheckedExceptionFromServiceSpecificException(Landroid/os/ServiceSpecificException;)Ljava/io/IOException;
-Landroid/net/IpSecManager;->rethrowUncheckedExceptionFromServiceSpecificException(Landroid/os/ServiceSpecificException;)Ljava/lang/RuntimeException;
-Landroid/net/IpSecManager;->TAG:Ljava/lang/String;
-Landroid/net/IpSecSpiResponse;-><init>(I)V
-Landroid/net/IpSecSpiResponse;-><init>(III)V
-Landroid/net/IpSecSpiResponse;-><init>(Landroid/os/Parcel;)V
-Landroid/net/IpSecSpiResponse;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/IpSecSpiResponse;->resourceId:I
-Landroid/net/IpSecSpiResponse;->spi:I
-Landroid/net/IpSecSpiResponse;->status:I
-Landroid/net/IpSecSpiResponse;->TAG:Ljava/lang/String;
-Landroid/net/IpSecTransform$Builder;->buildTunnelModeTransform(Ljava/net/InetAddress;Landroid/net/IpSecManager$SecurityParameterIndex;)Landroid/net/IpSecTransform;
-Landroid/net/IpSecTransform$Builder;->mConfig:Landroid/net/IpSecConfig;
-Landroid/net/IpSecTransform$Builder;->mContext:Landroid/content/Context;
-Landroid/net/IpSecTransform$NattKeepaliveCallback;-><init>()V
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_HARDWARE_ERROR:I
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_HARDWARE_UNSUPPORTED:I
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_INVALID_NETWORK:I
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->onError(I)V
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->onStarted()V
-Landroid/net/IpSecTransform$NattKeepaliveCallback;->onStopped()V
-Landroid/net/IpSecTransform;-><init>(Landroid/content/Context;Landroid/net/IpSecConfig;)V
-Landroid/net/IpSecTransform;->activate()Landroid/net/IpSecTransform;
-Landroid/net/IpSecTransform;->checkResultStatus(I)V
-Landroid/net/IpSecTransform;->ENCAP_ESPINUDP:I
-Landroid/net/IpSecTransform;->ENCAP_ESPINUDP_NON_IKE:I
-Landroid/net/IpSecTransform;->ENCAP_NONE:I
-Landroid/net/IpSecTransform;->equals(Landroid/net/IpSecTransform;Landroid/net/IpSecTransform;)Z
-Landroid/net/IpSecTransform;->getConfig()Landroid/net/IpSecConfig;
-Landroid/net/IpSecTransform;->getIpSecService()Landroid/net/IIpSecService;
-Landroid/net/IpSecTransform;->getResourceId()I
-Landroid/net/IpSecTransform;->mCallbackHandler:Landroid/os/Handler;
-Landroid/net/IpSecTransform;->mCloseGuard:Ldalvik/system/CloseGuard;
-Landroid/net/IpSecTransform;->mConfig:Landroid/net/IpSecConfig;
-Landroid/net/IpSecTransform;->mContext:Landroid/content/Context;
-Landroid/net/IpSecTransform;->mKeepalive:Landroid/net/ConnectivityManager$PacketKeepalive;
-Landroid/net/IpSecTransform;->mKeepaliveCallback:Landroid/net/ConnectivityManager$PacketKeepaliveCallback;
-Landroid/net/IpSecTransform;->MODE_TRANSPORT:I
-Landroid/net/IpSecTransform;->MODE_TUNNEL:I
-Landroid/net/IpSecTransform;->mResourceId:I
-Landroid/net/IpSecTransform;->mUserKeepaliveCallback:Landroid/net/IpSecTransform$NattKeepaliveCallback;
-Landroid/net/IpSecTransform;->startNattKeepalive(Landroid/net/IpSecTransform$NattKeepaliveCallback;ILandroid/os/Handler;)V
-Landroid/net/IpSecTransform;->stopNattKeepalive()V
-Landroid/net/IpSecTransform;->TAG:Ljava/lang/String;
-Landroid/net/IpSecTransformResponse;-><init>(I)V
-Landroid/net/IpSecTransformResponse;-><init>(II)V
-Landroid/net/IpSecTransformResponse;-><init>(Landroid/os/Parcel;)V
-Landroid/net/IpSecTransformResponse;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/IpSecTransformResponse;->resourceId:I
-Landroid/net/IpSecTransformResponse;->status:I
-Landroid/net/IpSecTransformResponse;->TAG:Ljava/lang/String;
-Landroid/net/IpSecTunnelInterfaceResponse;-><init>(I)V
-Landroid/net/IpSecTunnelInterfaceResponse;-><init>(IILjava/lang/String;)V
-Landroid/net/IpSecTunnelInterfaceResponse;-><init>(Landroid/os/Parcel;)V
-Landroid/net/IpSecTunnelInterfaceResponse;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/IpSecTunnelInterfaceResponse;->interfaceName:Ljava/lang/String;
-Landroid/net/IpSecTunnelInterfaceResponse;->resourceId:I
-Landroid/net/IpSecTunnelInterfaceResponse;->status:I
-Landroid/net/IpSecTunnelInterfaceResponse;->TAG:Ljava/lang/String;
-Landroid/net/IpSecUdpEncapResponse;-><init>(I)V
-Landroid/net/IpSecUdpEncapResponse;-><init>(IIILjava/io/FileDescriptor;)V
-Landroid/net/IpSecUdpEncapResponse;-><init>(Landroid/os/Parcel;)V
-Landroid/net/IpSecUdpEncapResponse;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/IpSecUdpEncapResponse;->fileDescriptor:Landroid/os/ParcelFileDescriptor;
-Landroid/net/IpSecUdpEncapResponse;->port:I
-Landroid/net/IpSecUdpEncapResponse;->resourceId:I
-Landroid/net/IpSecUdpEncapResponse;->status:I
-Landroid/net/IpSecUdpEncapResponse;->TAG:Ljava/lang/String;
Landroid/net/ITetheringStatsProvider$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/net/ITetheringStatsProvider$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
Landroid/net/ITetheringStatsProvider$Stub$Proxy;->getTetherStats(I)Landroid/net/NetworkStats;
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/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index dd33fdf..c8d2e0e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -280,7 +280,7 @@
// Lower privacy policy (less restrictive) wins.
report->set_privacy_policy(args.getPrivacyPolicy());
}
- report->set_all_sections(report->all_sections() | args.all());
+ report->set_all_sections(report->all_sections() || args.all());
for (int section: args.sections()) {
if (!has_section(*report, section)) {
report->add_section(section);
diff --git a/core/api/current.txt b/core/api/current.txt
index 0316875..a9f1d45 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";
@@ -327,6 +331,9 @@
field public static final int allowClearUserData = 16842757; // 0x1010005
field public static final int allowClickWhenDisabled = 16844312; // 0x1010618
field public static final int allowEmbedded = 16843765; // 0x10103f5
+ field public static final int allowGameAngleDriver;
+ field public static final int allowGameDownscaling;
+ field public static final int allowGameFpsOverride;
field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
@@ -362,6 +369,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
@@ -729,6 +737,10 @@
field public static final int freezesText = 16843116; // 0x101016c
field public static final int fromAlpha = 16843210; // 0x10101ca
field public static final int fromDegrees = 16843187; // 0x10101b3
+ field public static final int fromExtendBottom;
+ field public static final int fromExtendLeft;
+ field public static final int fromExtendRight;
+ field public static final int fromExtendTop;
field public static final int fromId = 16843850; // 0x101044a
field public static final int fromScene = 16843741; // 0x10103dd
field public static final int fromXDelta = 16843206; // 0x10101c6
@@ -1431,10 +1443,12 @@
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportedTypes;
field public static final int supportsAssist = 16844016; // 0x10104f0
+ field public static final int supportsBatteryGameMode;
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
+ field public static final int supportsPerformanceGameMode;
field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsStylusHandwriting;
@@ -1574,6 +1588,10 @@
field public static final int titleTextStyle = 16843512; // 0x10102f8
field public static final int toAlpha = 16843211; // 0x10101cb
field public static final int toDegrees = 16843188; // 0x10101b4
+ field public static final int toExtendBottom;
+ field public static final int toExtendLeft;
+ field public static final int toExtendRight;
+ field public static final int toExtendTop;
field public static final int toId = 16843849; // 0x1010449
field public static final int toScene = 16843742; // 0x10103de
field public static final int toXDelta = 16843207; // 0x10101c7
@@ -2103,6 +2121,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
@@ -3059,6 +3079,7 @@
method public void onSystemActionsChanged();
method public final boolean performGlobalAction(int);
method public void setAccessibilityFocusAppearance(int, @ColorInt int);
+ method public void setAnimationScale(float);
method public boolean setCacheEnabled(boolean);
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
@@ -3120,6 +3141,11 @@
field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
+ field public static final int GLOBAL_ACTION_DPAD_CENTER = 20; // 0x14
+ field public static final int GLOBAL_ACTION_DPAD_DOWN = 17; // 0x11
+ field public static final int GLOBAL_ACTION_DPAD_LEFT = 18; // 0x12
+ field public static final int GLOBAL_ACTION_DPAD_RIGHT = 19; // 0x13
+ field public static final int GLOBAL_ACTION_DPAD_UP = 16; // 0x10
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -3145,22 +3171,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);
}
@@ -3571,17 +3597,17 @@
}
public static interface Animator.AnimatorListener {
- method public void onAnimationCancel(android.animation.Animator);
- method public default void onAnimationEnd(android.animation.Animator, boolean);
- method public void onAnimationEnd(android.animation.Animator);
- method public void onAnimationRepeat(android.animation.Animator);
- method public default void onAnimationStart(android.animation.Animator, boolean);
- method public void onAnimationStart(android.animation.Animator);
+ method public void onAnimationCancel(@NonNull android.animation.Animator);
+ method public default void onAnimationEnd(@NonNull android.animation.Animator, boolean);
+ method public void onAnimationEnd(@NonNull android.animation.Animator);
+ method public void onAnimationRepeat(@NonNull android.animation.Animator);
+ method public default void onAnimationStart(@NonNull android.animation.Animator, boolean);
+ method public void onAnimationStart(@NonNull android.animation.Animator);
}
public static interface Animator.AnimatorPauseListener {
- method public void onAnimationPause(android.animation.Animator);
- method public void onAnimationResume(android.animation.Animator);
+ method public void onAnimationPause(@NonNull android.animation.Animator);
+ method public void onAnimationResume(@NonNull android.animation.Animator);
}
public class AnimatorInflater {
@@ -3868,7 +3894,7 @@
}
public static interface ValueAnimator.AnimatorUpdateListener {
- method public void onAnimationUpdate(android.animation.ValueAnimator);
+ method public void onAnimationUpdate(@NonNull android.animation.ValueAnimator);
}
}
@@ -4049,7 +4075,7 @@
method public int getMaxNumPictureInPictureActions();
method public final android.media.session.MediaController getMediaController();
method @NonNull public android.view.MenuInflater getMenuInflater();
- method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
+ method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
method public final android.app.Activity getParent();
method @Nullable public android.content.Intent getParentActivityIntent();
method public android.content.SharedPreferences getPreferences(int);
@@ -4176,6 +4202,7 @@
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
method public void overridePendingTransition(int, int);
+ method public void overridePendingTransition(int, int, int);
method public void postponeEnterTransition();
method public void recreate();
method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
@@ -4479,6 +4506,7 @@
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+ method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int);
method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...);
@@ -4853,6 +4881,7 @@
field public static final int REASON_DEPENDENCY_DIED = 12; // 0xc
field public static final int REASON_EXCESSIVE_RESOURCE_USAGE = 9; // 0x9
field public static final int REASON_EXIT_SELF = 1; // 0x1
+ field public static final int REASON_FREEZER = 14; // 0xe
field public static final int REASON_INITIALIZATION_FAILURE = 7; // 0x7
field public static final int REASON_LOW_MEMORY = 3; // 0x3
field public static final int REASON_OTHER = 13; // 0xd
@@ -4948,7 +4977,7 @@
method @NonNull @UiContext public final android.content.Context getContext();
method @Nullable public android.view.View getCurrentFocus();
method @NonNull public android.view.LayoutInflater getLayoutInflater();
- method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
+ method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
method @Nullable public final android.app.Activity getOwnerActivity();
method @Nullable public final android.view.SearchEvent getSearchEvent();
method public final int getVolumeControlStream();
@@ -6927,6 +6956,7 @@
method public boolean performGlobalAction(int);
method public void revokeRuntimePermission(String, String);
method public void revokeRuntimePermissionAsUser(String, String, android.os.UserHandle);
+ method public void setAnimationScale(float);
method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener);
method public boolean setRotation(int);
method public void setRunAsMonkey(boolean);
@@ -7302,10 +7332,10 @@
method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
- method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
- method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+ method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
method @NonNull public String getEnrollmentSpecificId();
method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -7589,7 +7619,7 @@
field public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
- field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
+ field @Deprecated public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
field @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
field public static final String EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT";
@@ -7908,11 +7938,11 @@
}
public final class WifiSsidPolicy implements android.os.Parcelable {
- method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<java.lang.String>);
- method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<android.net.wifi.WifiSsid>);
+ method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<android.net.wifi.WifiSsid>);
method public int describeContents();
method public int getPolicyType();
- method @NonNull public java.util.Set<java.lang.String> getSsids();
+ method @NonNull public java.util.Set<android.net.wifi.WifiSsid> getSsids();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.WifiSsidPolicy> CREATOR;
field public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; // 0x0
@@ -8890,9 +8920,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 +8937,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 +8974,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 +8998,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";
}
@@ -9797,7 +9832,6 @@
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String STORAGE_SERVICE = "storage";
field public static final String STORAGE_STATS_SERVICE = "storagestats";
- field public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process";
field public static final String SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final String TELECOM_SERVICE = "telecom";
field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
@@ -11876,6 +11910,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";
@@ -11901,7 +11936,7 @@
field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
field public static final String FEATURE_TELEPHONY_MESSAGING = "android.hardware.telephony.messaging";
- field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio";
+ field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
@@ -12210,7 +12245,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
field public static final int TYPE_BUILTIN = 0; // 0x0
field public static final int TYPE_DYNAMIC = 1; // 0x1
- field public static final int TYPE_SDK = 3; // 0x3
+ field public static final int TYPE_SDK_PACKAGE = 3; // 0x3
field public static final int TYPE_STATIC = 2; // 0x2
field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
}
@@ -12938,6 +12973,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.database.CursorWindow> CREATOR;
}
+ public class CursorWindowAllocationException extends java.lang.RuntimeException {
+ ctor public CursorWindowAllocationException(@NonNull String);
+ }
+
public class CursorWrapper implements android.database.Cursor {
ctor public CursorWrapper(android.database.Cursor);
method public void close();
@@ -13969,6 +14008,7 @@
enum_constant @Deprecated public static final android.graphics.Bitmap.Config ARGB_4444;
enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+ enum_constant public static final android.graphics.Bitmap.Config RGBA_1010102;
enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
enum_constant public static final android.graphics.Bitmap.Config RGB_565;
}
@@ -15733,9 +15773,9 @@
method public final void copyBounds(@NonNull android.graphics.Rect);
method @NonNull public final android.graphics.Rect copyBounds();
method @Nullable public static android.graphics.drawable.Drawable createFromPath(String);
- method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, String);
+ method @Nullable public static android.graphics.drawable.Drawable createFromResourceStream(@Nullable android.content.res.Resources, @Nullable android.util.TypedValue, @Nullable java.io.InputStream, @Nullable String);
method @Deprecated @Nullable public static android.graphics.drawable.Drawable createFromResourceStream(@Nullable android.content.res.Resources, @Nullable android.util.TypedValue, @Nullable java.io.InputStream, @Nullable String, @Nullable android.graphics.BitmapFactory.Options);
- method public static android.graphics.drawable.Drawable createFromStream(java.io.InputStream, String);
+ method @Nullable public static android.graphics.drawable.Drawable createFromStream(@Nullable java.io.InputStream, @Nullable String);
method @NonNull public static android.graphics.drawable.Drawable createFromXml(@NonNull android.content.res.Resources, @NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @NonNull public static android.graphics.drawable.Drawable createFromXml(@NonNull android.content.res.Resources, @NonNull org.xmlpull.v1.XmlPullParser, @Nullable android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @NonNull public static android.graphics.drawable.Drawable createFromXmlInner(@NonNull android.content.res.Resources, @NonNull org.xmlpull.v1.XmlPullParser, @NonNull android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -15773,10 +15813,10 @@
method public final boolean isVisible();
method public void jumpToCurrentState();
method @NonNull public android.graphics.drawable.Drawable mutate();
- method protected void onBoundsChange(android.graphics.Rect);
+ method protected void onBoundsChange(@NonNull android.graphics.Rect);
method public boolean onLayoutDirectionChanged(int);
method protected boolean onLevelChange(int);
- method protected boolean onStateChange(int[]);
+ method protected boolean onStateChange(@NonNull int[]);
method public static int resolveOpacity(int, int);
method public void scheduleSelf(@NonNull Runnable, long);
method public abstract void setAlpha(@IntRange(from=0, to=255) int);
@@ -15937,27 +15977,27 @@
}
public final class Icon implements android.os.Parcelable {
- method public static android.graphics.drawable.Icon createWithAdaptiveBitmap(android.graphics.Bitmap);
+ method @NonNull public static android.graphics.drawable.Icon createWithAdaptiveBitmap(android.graphics.Bitmap);
method @NonNull public static android.graphics.drawable.Icon createWithAdaptiveBitmapContentUri(@NonNull String);
method @NonNull public static android.graphics.drawable.Icon createWithAdaptiveBitmapContentUri(@NonNull android.net.Uri);
- method public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap);
- method public static android.graphics.drawable.Icon createWithContentUri(String);
- method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
- method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
- method public static android.graphics.drawable.Icon createWithFilePath(String);
- method public static android.graphics.drawable.Icon createWithResource(android.content.Context, @DrawableRes int);
- method public static android.graphics.drawable.Icon createWithResource(String, @DrawableRes int);
+ method @NonNull public static android.graphics.drawable.Icon createWithBitmap(android.graphics.Bitmap);
+ method @NonNull public static android.graphics.drawable.Icon createWithContentUri(String);
+ method @NonNull public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
+ method @NonNull public static android.graphics.drawable.Icon createWithData(byte[], int, int);
+ method @NonNull public static android.graphics.drawable.Icon createWithFilePath(String);
+ method @NonNull public static android.graphics.drawable.Icon createWithResource(android.content.Context, @DrawableRes int);
+ method @NonNull public static android.graphics.drawable.Icon createWithResource(String, @DrawableRes int);
method public int describeContents();
method @DrawableRes public int getResId();
method @NonNull public String getResPackage();
method public int getType();
method @NonNull public android.net.Uri getUri();
- method public android.graphics.drawable.Drawable loadDrawable(android.content.Context);
- method public void loadDrawableAsync(android.content.Context, android.os.Message);
- method public void loadDrawableAsync(android.content.Context, android.graphics.drawable.Icon.OnDrawableLoadedListener, android.os.Handler);
- method public android.graphics.drawable.Icon setTint(@ColorInt int);
+ method @Nullable public android.graphics.drawable.Drawable loadDrawable(android.content.Context);
+ method public void loadDrawableAsync(@NonNull android.content.Context, @NonNull android.os.Message);
+ method public void loadDrawableAsync(@NonNull android.content.Context, android.graphics.drawable.Icon.OnDrawableLoadedListener, android.os.Handler);
+ method @NonNull public android.graphics.drawable.Icon setTint(@ColorInt int);
method @NonNull public android.graphics.drawable.Icon setTintBlendMode(@NonNull android.graphics.BlendMode);
- method public android.graphics.drawable.Icon setTintList(android.content.res.ColorStateList);
+ method @NonNull public android.graphics.drawable.Icon setTintList(android.content.res.ColorStateList);
method @NonNull public android.graphics.drawable.Icon setTintMode(@NonNull android.graphics.PorterDuff.Mode);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.drawable.Icon> CREATOR;
@@ -16798,7 +16838,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 +18698,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);
@@ -19643,10 +19684,13 @@
method public android.media.AudioAttributes.Builder setUsage(int);
}
- public class AudioDescriptor {
+ public class AudioDescriptor implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public byte[] getDescriptor();
method public int getEncapsulationType();
method public int getStandard();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDescriptor> CREATOR;
field public static final int STANDARD_EDID = 1; // 0x1
field public static final int STANDARD_NONE = 0; // 0x0
}
@@ -19859,6 +19903,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();
@@ -20161,14 +20206,17 @@
method @NonNull public android.media.AudioPresentation.Builder setProgramId(int);
}
- public class AudioProfile {
+ public class AudioProfile implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public int[] getChannelIndexMasks();
method @NonNull public int[] getChannelMasks();
method public int getEncapsulationType();
method public int getFormat();
method @NonNull public int[] getSampleRates();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; // 0x1
field public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioProfile> CREATOR;
}
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
@@ -20559,13 +20607,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 +21280,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 +21407,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 +21713,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 +21728,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 +22733,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 {
@@ -22687,6 +22754,7 @@
method public int describeContents();
method @Nullable public String getClientPackageName();
method public int getConnectionState();
+ method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds();
method @Nullable public CharSequence getDescription();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public java.util.List<java.lang.String> getFeatures();
@@ -22720,6 +22788,7 @@
method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
@@ -23246,8 +23315,12 @@
public final class RouteDiscoveryPreference implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
+ method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
method public boolean shouldPerformActiveScan();
+ method public boolean shouldRemoveDuplicates();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
}
@@ -23256,7 +23329,10 @@
ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
}
@@ -24933,6 +25009,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 +25645,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 +25742,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 +25762,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);
@@ -25679,6 +25916,27 @@
package android.media.tv.interactive {
+ public final class AppLinkInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getClassName();
+ method @NonNull public String getPackageName();
+ method @Nullable public String getUriHost();
+ method @Nullable public String getUriPrefix();
+ method @Nullable public String getUriScheme();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.interactive.AppLinkInfo> CREATOR;
+ }
+
+ public static final class AppLinkInfo.Builder {
+ ctor public AppLinkInfo.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo build();
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setClassName(@NonNull String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setPackageName(@NonNull String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriHost(@Nullable String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriPrefix(@Nullable String);
+ method @NonNull public android.media.tv.interactive.AppLinkInfo.Builder setUriScheme(@Nullable String);
+ }
+
public final class TvInteractiveAppInfo implements android.os.Parcelable {
ctor public TvInteractiveAppInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
method public int describeContents();
@@ -25695,10 +25953,17 @@
public final class TvInteractiveAppManager {
method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppInfo> getTvInteractiveAppServiceList();
method public void prepare(@NonNull String, int);
+ method public void registerAppLinkInfo(@NonNull String, @NonNull android.media.tv.interactive.AppLinkInfo);
method public void registerCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback, @NonNull java.util.concurrent.Executor);
method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+ method public void unregisterAppLinkInfo(@NonNull String, @NonNull android.media.tv.interactive.AppLinkInfo);
method public void unregisterCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback);
field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.interactive.action.APP_LINK_COMMAND";
+ field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+ field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+ field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+ field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+ field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
field public static final int ERROR_BLOCKED = 5; // 0x5
field public static final int ERROR_ENCRYPTED = 6; // 0x6
field public static final int ERROR_NONE = 0; // 0x0
@@ -25715,11 +25980,6 @@
field public static final int INTERACTIVE_APP_STATE_ERROR = 3; // 0x3
field public static final int INTERACTIVE_APP_STATE_RUNNING = 2; // 0x2
field public static final int INTERACTIVE_APP_STATE_STOPPED = 1; // 0x1
- field public static final String KEY_BACK_URI = "back_uri";
- field public static final String KEY_CLASS_NAME = "class_name";
- field public static final String KEY_COMMAND_TYPE = "command_type";
- field public static final String KEY_PACKAGE_NAME = "package_name";
- field public static final String KEY_SERVICE_ID = "service_id";
field public static final int SERVICE_STATE_ERROR = 4; // 0x4
field public static final int SERVICE_STATE_PREPARING = 2; // 0x2
field public static final int SERVICE_STATE_READY = 3; // 0x3
@@ -25744,6 +26004,8 @@
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public abstract android.media.tv.interactive.TvInteractiveAppService.Session onCreateSession(@NonNull String, int);
method public abstract void onPrepare(int);
+ method public void onRegisterAppLinkInfo(@NonNull android.media.tv.interactive.AppLinkInfo);
+ method public void onUnregisterAppLinkInfo(@NonNull android.media.tv.interactive.AppLinkInfo);
field public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = "command_change_channel_quietly";
field public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
field public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
@@ -25767,6 +26029,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);
@@ -25798,7 +26061,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();
@@ -26127,86 +26392,19 @@
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);
}
- public final class IpSecAlgorithm implements android.os.Parcelable {
- ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]);
- ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int);
- method public int describeContents();
- method @NonNull public byte[] getKey();
- method @NonNull public String getName();
- method @NonNull public static java.util.Set<java.lang.String> getSupportedAlgorithms();
- method public int getTruncationLengthBits();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final String AUTH_AES_CMAC = "cmac(aes)";
- field public static final String AUTH_AES_XCBC = "xcbc(aes)";
- field public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
- field public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
- field public static final String AUTH_HMAC_MD5 = "hmac(md5)";
- field public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
- field public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
- field public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
- field public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
- field @NonNull public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
- field public static final String CRYPT_AES_CBC = "cbc(aes)";
- field public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
- }
-
- public final class IpSecManager {
- method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
- method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public void applyTransportModeTransform(@NonNull java.net.Socket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
- method public void applyTransportModeTransform(@NonNull java.net.DatagramSocket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
- method public void applyTransportModeTransform(@NonNull java.io.FileDescriptor, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
- method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public void removeTransportModeTransforms(@NonNull java.net.Socket) throws java.io.IOException;
- method public void removeTransportModeTransforms(@NonNull java.net.DatagramSocket) throws java.io.IOException;
- method public void removeTransportModeTransforms(@NonNull java.io.FileDescriptor) throws java.io.IOException;
- field public static final int DIRECTION_IN = 0; // 0x0
- field public static final int DIRECTION_OUT = 1; // 0x1
- }
-
- public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
- }
-
- public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
- method public void close();
- method public int getSpi();
- }
-
- public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
- method public int getSpi();
- }
-
- public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close() throws java.io.IOException;
- method public java.io.FileDescriptor getFileDescriptor();
- method public int getPort();
- }
-
- public final class IpSecTransform implements java.lang.AutoCloseable {
- method public void close();
- }
-
- public static class IpSecTransform.Builder {
- ctor public IpSecTransform.Builder(@NonNull android.content.Context);
- method @NonNull public android.net.IpSecTransform buildTransportModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method @NonNull public android.net.IpSecTransform.Builder setAuthenticatedEncryption(@NonNull android.net.IpSecAlgorithm);
- method @NonNull public android.net.IpSecTransform.Builder setAuthentication(@NonNull android.net.IpSecAlgorithm);
- method @NonNull public android.net.IpSecTransform.Builder setEncryption(@NonNull android.net.IpSecAlgorithm);
- method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int);
- }
-
public class LocalServerSocket implements java.io.Closeable {
ctor public LocalServerSocket(String) throws java.io.IOException;
ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException;
@@ -26278,6 +26476,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
@@ -31180,6 +31379,9 @@
method @Nullable public byte[] createByteArray();
method @Nullable public char[] createCharArray();
method @Nullable public double[] createDoubleArray();
+ method @Nullable public <T> T createFixedArray(@NonNull Class<T>, @NonNull int...);
+ method @Nullable public <T, S extends android.os.IInterface> T createFixedArray(@NonNull Class<T>, @NonNull java.util.function.Function<android.os.IBinder,S>, @NonNull int...);
+ method @Nullable public <T, S extends android.os.Parcelable> T createFixedArray(@NonNull Class<T>, @NonNull android.os.Parcelable.Creator<S>, @NonNull int...);
method @Nullable public float[] createFloatArray();
method @Nullable public int[] createIntArray();
method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
@@ -31221,6 +31423,9 @@
method public void readException();
method public void readException(int, String);
method public android.os.ParcelFileDescriptor readFileDescriptor();
+ method public <T> void readFixedArray(@NonNull T);
+ method public <T, S extends android.os.IInterface> void readFixedArray(@NonNull T, @NonNull java.util.function.Function<android.os.IBinder,S>);
+ method public <T, S extends android.os.Parcelable> void readFixedArray(@NonNull T, @NonNull android.os.Parcelable.Creator<S>);
method public float readFloat();
method public void readFloatArray(@NonNull float[]);
method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
@@ -31236,7 +31441,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);
@@ -31246,7 +31451,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);
@@ -31264,6 +31469,7 @@
method public void setDataCapacity(int);
method public void setDataPosition(int);
method public void setDataSize(int);
+ method public void setPropagateAllowBlocking();
method public void unmarshall(@NonNull byte[], int, int);
method public void writeArray(@Nullable Object[]);
method public void writeBinderArray(@Nullable android.os.IBinder[]);
@@ -31281,6 +31487,7 @@
method public void writeDoubleArray(@Nullable double[]);
method public void writeException(@NonNull Exception);
method public void writeFileDescriptor(@NonNull java.io.FileDescriptor);
+ method public <T> void writeFixedArray(@Nullable T, int, @NonNull int...);
method public void writeFloat(float);
method public void writeFloatArray(@Nullable float[]);
method public void writeInt(int);
@@ -31455,6 +31662,7 @@
method public boolean isDeviceLightIdleMode();
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
+ method public boolean isLowPowerStandbyEnabled();
method public boolean isPowerSaveMode();
method public boolean isRebootingUserspaceSupported();
method @Deprecated public boolean isScreenOn();
@@ -31466,6 +31674,7 @@
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+ field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -31499,9 +31708,14 @@
method public void release();
method public void release(int);
method public void setReferenceCounted(boolean);
+ method public void setStateListener(@NonNull java.util.concurrent.Executor, @Nullable android.os.PowerManager.WakeLockStateListener);
method public void setWorkSource(android.os.WorkSource);
}
+ public static interface PowerManager.WakeLockStateListener {
+ method public void onStateChanged(boolean);
+ }
+
public class Process {
ctor public Process();
method public static final long getElapsedCpuTime();
@@ -32190,6 +32404,7 @@
method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public java.util.List<android.os.storage.StorageVolume> getStorageVolumesIncludingSharedProfiles();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor);
method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException;
@@ -32223,6 +32438,7 @@
method public String getDescription(android.content.Context);
method @Nullable public java.io.File getDirectory();
method @Nullable public String getMediaStoreVolumeName();
+ method @NonNull public android.os.UserHandle getOwner();
method public String getState();
method @Nullable public java.util.UUID getStorageUuid();
method @Nullable public String getUuid();
@@ -37650,6 +37866,7 @@
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+ field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
}
@@ -37700,21 +37917,23 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(@NonNull android.widget.RemoteViews);
+ ctor @Deprecated public Dataset.Builder(@NonNull android.widget.RemoteViews);
+ ctor public Dataset.Builder(@NonNull android.service.autofill.Presentations);
ctor public Dataset.Builder();
method @NonNull public android.service.autofill.Dataset build();
method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender);
+ method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field);
method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String);
- method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
}
public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -37731,6 +37950,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.DateValueSanitizer> CREATOR;
}
+ public final class Field {
+ method @Nullable public android.service.autofill.Presentations getPresentations();
+ method @Nullable public android.view.autofill.AutofillValue getValue();
+ }
+
+ public static final class Field.Builder {
+ ctor public Field.Builder();
+ method @NonNull public android.service.autofill.Field build();
+ method @NonNull public android.service.autofill.Field.Builder setFilter(@Nullable java.util.regex.Pattern);
+ method @NonNull public android.service.autofill.Field.Builder setPresentations(@NonNull android.service.autofill.Presentations);
+ method @NonNull public android.service.autofill.Field.Builder setValue(@NonNull android.view.autofill.AutofillValue);
+ }
+
public final class FieldClassification {
method @NonNull public java.util.List<android.service.autofill.FieldClassification.Match> getMatches();
}
@@ -37790,6 +38022,7 @@
public final class FillRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.os.Bundle getClientState();
+ method @Nullable public android.content.IntentSender getDelayedFillIntentSender();
method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
method public int getFlags();
method public int getId();
@@ -37804,6 +38037,7 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+ field public static final int FLAG_DELAY_FILL = 4; // 0x4
field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
}
@@ -37813,11 +38047,14 @@
method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
- method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.service.autofill.Presentations);
method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
+ method @NonNull public android.service.autofill.FillResponse.Builder setDialogHeader(@NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
+ method @NonNull public android.service.autofill.FillResponse.Builder setFillDialogTriggerIds(@NonNull android.view.autofill.AutofillId...);
method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews);
@@ -37862,6 +38099,22 @@
public interface OnClickAction {
}
+ public final class Presentations {
+ method @Nullable public android.widget.RemoteViews getDialogPresentation();
+ method @Nullable public android.service.autofill.InlinePresentation getInlinePresentation();
+ method @Nullable public android.service.autofill.InlinePresentation getInlineTooltipPresentation();
+ method @Nullable public android.widget.RemoteViews getMenuPresentation();
+ }
+
+ public static final class Presentations.Builder {
+ ctor public Presentations.Builder();
+ method @NonNull public android.service.autofill.Presentations build();
+ method @NonNull public android.service.autofill.Presentations.Builder setDialogPresentation(@NonNull android.widget.RemoteViews);
+ method @NonNull public android.service.autofill.Presentations.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Presentations.Builder setInlineTooltipPresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Presentations.Builder setMenuPresentation(@NonNull android.widget.RemoteViews);
+ }
+
public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
ctor public RegexValidator(@NonNull android.view.autofill.AutofillId, @NonNull java.util.regex.Pattern);
method public int describeContents();
@@ -40930,6 +41183,7 @@
field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+ field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
@@ -41014,6 +41268,7 @@
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+ field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -41057,6 +41312,7 @@
field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
+ field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
field public static final int SERVICE_CLASS_NONE = 0; // 0x0
field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
@@ -42698,6 +42954,7 @@
field public static final int ENCODING_16BIT = 3; // 0x3
field public static final int ENCODING_7BIT = 1; // 0x1
field public static final int ENCODING_8BIT = 2; // 0x2
+ field public static final int ENCODING_KSC5601 = 4; // 0x4
field public static final int ENCODING_UNKNOWN = 0; // 0x0
field public static final String FORMAT_3GPP = "3gpp";
field public static final String FORMAT_3GPP2 = "3gpp2";
@@ -42975,6 +43232,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
method public int getActiveModemCount();
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public long getAllowedNetworkTypesForReason(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCallComposerStatus();
method @Deprecated @RequiresPermission(value=android.Manifest.permission.READ_PHONE_STATE, conditional=true) public int getCallState();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getCallStateForSubscription();
@@ -43035,6 +43293,7 @@
method public int getSubscriptionId();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getSubscriptionId(@NonNull android.telecom.PhoneAccountHandle);
method public int getSupportedModemCount();
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public long getSupportedRadioAccessFamily();
method @Nullable public String getTypeAllocationCode();
method @Nullable public String getTypeAllocationCode(int);
method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo();
@@ -43080,6 +43339,7 @@
method public String sendEnvelopeWithStatus(String);
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAllowedNetworkTypesForReason(int, long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
@@ -43116,6 +43376,8 @@
field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
field public static final String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED";
field public static final String ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED";
+ field public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; // 0x2
+ field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0
field public static final int APPTYPE_CSIM = 4; // 0x4
field public static final int APPTYPE_ISIM = 5; // 0x5
field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -43190,6 +43452,26 @@
field public static final int NETWORK_SELECTION_MODE_MANUAL = 2; // 0x2
field public static final int NETWORK_SELECTION_MODE_UNKNOWN = 0; // 0x0
field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
+ field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
+ field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
+ field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
+ field public static final long NETWORK_TYPE_BITMASK_EHRPD = 8192L; // 0x2000L
+ field public static final long NETWORK_TYPE_BITMASK_EVDO_0 = 16L; // 0x10L
+ field public static final long NETWORK_TYPE_BITMASK_EVDO_A = 32L; // 0x20L
+ field public static final long NETWORK_TYPE_BITMASK_EVDO_B = 2048L; // 0x800L
+ field public static final long NETWORK_TYPE_BITMASK_GPRS = 1L; // 0x1L
+ field public static final long NETWORK_TYPE_BITMASK_GSM = 32768L; // 0x8000L
+ field public static final long NETWORK_TYPE_BITMASK_HSDPA = 128L; // 0x80L
+ field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
+ field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
+ field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
+ field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
+ field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
+ field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
+ field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
+ field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
+ field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
+ field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
field public static final int NETWORK_TYPE_EHRPD = 14; // 0xe
@@ -48661,7 +48943,7 @@
}
public interface OnBackInvokedDispatcherOwner {
- method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
+ method @NonNull public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
}
public interface OnReceiveContentListener {
@@ -48940,6 +49222,8 @@
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public int describeContents();
+ method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration);
+ method public void notifyDetachedFromWindow();
method public void release();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControlViewHost.SurfacePackage> CREATOR;
@@ -49087,7 +49371,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.VerifiedMotionEvent> CREATOR;
}
- @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
+ @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
ctor public View(android.content.Context);
ctor public View(android.content.Context, @Nullable android.util.AttributeSet);
ctor public View(android.content.Context, @Nullable android.util.AttributeSet, int);
@@ -49291,7 +49575,6 @@
method @IdRes public int getNextFocusLeftId();
method @IdRes public int getNextFocusRightId();
method @IdRes public int getNextFocusUpId();
- method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
method @ColorInt public int getOutlineAmbientShadowColor();
method public android.view.ViewOutlineProvider getOutlineProvider();
@@ -49395,6 +49678,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();
@@ -49578,6 +49862,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);
@@ -51182,6 +51467,7 @@
method public CharSequence getPackageName();
method public android.view.accessibility.AccessibilityRecord getRecord(int);
method public int getRecordCount();
+ method public int getSpeechStateChangeTypes();
method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
@@ -51193,6 +51479,7 @@
method public void setEventType(int);
method public void setMovementGranularity(int);
method public void setPackageName(CharSequence);
+ method public void setSpeechStateChangeTypes(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
@@ -51208,12 +51495,17 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityEvent> CREATOR;
field public static final int INVALID_POSITION = -1; // 0xffffffff
field @Deprecated public static final int MAX_TEXT_LENGTH = 500; // 0x1f4
+ field public static final int SPEECH_STATE_LISTENING_END = 8; // 0x8
+ field public static final int SPEECH_STATE_LISTENING_START = 4; // 0x4
+ field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
+ field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
field public static final int TYPE_NOTIFICATION_STATE_CHANGED = 64; // 0x40
+ field public static final int TYPE_SPEECH_STATE_CHANGE = 33554432; // 0x2000000
field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
field public static final int TYPE_TOUCH_INTERACTION_END = 2097152; // 0x200000
@@ -51373,6 +51665,7 @@
method public boolean isSelected();
method public boolean isShowingHintText();
method public boolean isTextEntryKey();
+ method public boolean isTextSelectable();
method public boolean isVisibleToUser();
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
@@ -51436,6 +51729,7 @@
method public void setStateDescription(@Nullable CharSequence);
method public void setText(CharSequence);
method public void setTextEntryKey(boolean);
+ method public void setTextSelectable(boolean);
method public void setTextSelection(int, int);
method public void setTooltipText(@Nullable CharSequence);
method public void setTouchDelegateInfo(@NonNull android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo);
@@ -52564,6 +52858,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
@@ -54069,7 +54366,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();
@@ -54114,7 +54410,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..9737cde 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -2,6 +2,7 @@
package android {
public static final class Manifest.permission {
+ field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
}
@@ -53,6 +54,21 @@
method public void onCanceled(@NonNull android.app.PendingIntent);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method public final void disableForCurrentProcess();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method @Nullable public final Result query(@NonNull Query);
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
@@ -62,9 +78,9 @@
package android.app.admin {
public class DevicePolicyManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getLogoutUser();
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int logoutUser();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int logoutUser();
field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
}
@@ -74,6 +90,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 +102,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 {
@@ -121,8 +139,14 @@
package android.content.pm {
+ public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos();
+ }
+
public abstract class PackageManager {
method @NonNull public String getPermissionControllerPackageName();
+ method @NonNull public String getSupplementalProcessPackageName();
+ field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
}
}
@@ -160,6 +184,8 @@
public class LocationManager {
method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public boolean injectLocation(@NonNull android.location.Location);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public boolean isAutomotiveGnssSuspended();
+ method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public void setAutomotiveGnssSuspended(boolean);
}
}
@@ -171,7 +197,7 @@
method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp();
method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio();
- method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo);
+ method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BluetoothProfileConnectionInfo);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean);
method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setHfpEnabled(boolean);
@@ -181,18 +207,18 @@
field public static final int FLAG_FROM_KEY = 4096; // 0x1000
}
- public final class BtProfileConnectionInfo implements android.os.Parcelable {
- method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int);
- method @NonNull public static android.media.BtProfileConnectionInfo a2dpSinkInfo(int);
+ public final class BluetoothProfileConnectionInfo implements android.os.Parcelable {
+ method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpInfo(boolean, int);
+ method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
+ method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
+ method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
method public int describeContents();
- method public boolean getIsLeOutput();
method public int getProfile();
- method public boolean getSuppressNoisyIntent();
method public int getVolume();
- method @NonNull public static android.media.BtProfileConnectionInfo hearingAidInfo(boolean);
- method @NonNull public static android.media.BtProfileConnectionInfo leAudio(boolean, boolean);
+ method public boolean isLeOutput();
+ method public boolean isSuppressNoisyIntent();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.media.BtProfileConnectionInfo> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.BluetoothProfileConnectionInfo> CREATOR;
}
public class MediaMetadataRetriever implements java.lang.AutoCloseable {
@@ -257,14 +283,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
}
- public final class IpSecManager {
- field public static final int DIRECTION_FWD = 2; // 0x2
- }
-
- public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public int getResourceId();
- }
-
public class LocalSocket implements java.io.Closeable {
ctor public LocalSocket(@NonNull java.io.FileDescriptor);
}
@@ -441,6 +459,17 @@
}
+package android.net.netstats {
+
+ public class NetworkStatsDataMigrationUtils {
+ method @NonNull public static android.net.NetworkStatsCollection readPlatformCollection(@NonNull String, long) throws java.io.IOException;
+ field public static final String PREFIX_UID = "uid";
+ field public static final String PREFIX_UID_TAG = "uid_tag";
+ field public static final String PREFIX_XT = "xt";
+ }
+
+}
+
package android.os {
public final class BatteryStatsManager {
@@ -536,10 +565,6 @@
field public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0; // 0x0
}
- public final class StorageVolume implements android.os.Parcelable {
- method @NonNull public android.os.UserHandle getOwner();
- }
-
}
package android.provider {
@@ -554,6 +579,26 @@
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
+ public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
+ field public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
+ field public static final String BLE_SCAN_BALANCED_INTERVAL_MS = "ble_scan_balanced_interval_ms";
+ field public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
+ field public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = "ble_scan_low_latency_interval_ms";
+ field public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = "ble_scan_low_latency_window_ms";
+ field public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = "ble_scan_low_power_interval_ms";
+ field public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
+ field public static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
+ field public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+ field public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
+ }
+
+ public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+ field public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
+ field public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
+ field public static final String BLUETOOTH_NAME = "bluetooth_name";
+ }
+
}
package android.telephony {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 311b110..07639fb 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -513,7 +513,7 @@
package android.view {
- @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
+ @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
method protected void initializeFadingEdge(android.content.res.TypedArray);
method protected void initializeScrollbars(android.content.res.TypedArray);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 55a6df2..71c9452 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";
@@ -35,7 +37,6 @@
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
- field public static final String AUTOMOTIVE_GNSS_CONTROLS = "android.permission.AUTOMOTIVE_GNSS_CONTROLS";
field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BACKUP = "android.permission.BACKUP";
field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
@@ -71,9 +72,11 @@
field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+ field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE";
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+ field public static final String BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE = "android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE";
field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
@@ -144,6 +147,7 @@
field public static final String KILL_UID = "android.permission.KILL_UID";
field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
+ field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS";
field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
@@ -157,14 +161,18 @@
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";
field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+ field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+ field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY";
field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY";
field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
@@ -185,6 +193,7 @@
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+ field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
@@ -197,6 +206,7 @@
field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+ field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE";
field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -254,6 +264,7 @@
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+ field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
@@ -269,9 +280,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";
@@ -301,6 +309,7 @@
field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
+ field public static final String SET_UNRESTRICTED_KEEP_CLEAR_AREAS = "android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS";
field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
@@ -347,6 +356,7 @@
field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+ field public static final String WRITE_SMS = "android.permission.WRITE_SMS";
}
public static final class Manifest.permission_group {
@@ -901,6 +911,19 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+ field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0
+ field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1; // 0x1
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0; // 0x0
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8; // 0x8
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6; // 0x6
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4; // 0x4
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2; // 0x2
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7; // 0x7
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5; // 0x5
+ field public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3; // 0x3
field public static final int NAV_BAR_MODE_OVERRIDE_KIDS = 1; // 0x1
field public static final int NAV_BAR_MODE_OVERRIDE_NONE = 0; // 0x0
}
@@ -1056,12 +1079,13 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
- method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
- method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
+ method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
+ method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
method public boolean isDeviceManaged();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean isDpcDownloaded();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk();
method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk();
@@ -1074,6 +1098,7 @@
method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>);
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
@@ -1152,6 +1177,13 @@
field public static final String UNDEFINED = "UNDEFINED";
}
+ public static final class DevicePolicyResources.Strings.Dialer {
+ field public static final String NOTIFICATION_INCOMING_WORK_CALL_TITLE = "Dialer.NOTIFICATION_INCOMING_WORK_CALL_TITLE";
+ field public static final String NOTIFICATION_MISSED_WORK_CALL_TITLE = "Dialer.NOTIFICATION_MISSED_WORK_CALL_TITLE";
+ field public static final String NOTIFICATION_ONGOING_WORK_CALL_TITLE = "Dialer.NOTIFICATION_ONGOING_WORK_CALL_TITLE";
+ field public static final String NOTIFICATION_WIFI_WORK_CALL_LABEL = "Dialer.NOTIFICATION_WIFI_WORK_CALL_LABEL";
+ }
+
public static final class DevicePolicyResources.Strings.DocumentsUi {
field public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE";
field public static final String CANT_SAVE_TO_PERSONAL_TITLE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE";
@@ -1181,6 +1213,15 @@
field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE";
}
+ public static final class DevicePolicyResources.Strings.PermissionController {
+ field public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+ field public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+ field public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+ field public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE = "PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+ field public static final String LOCATION_AUTO_GRANTED_MESSAGE = "PermissionController.LOCATION_AUTO_GRANTED_MESSAGE";
+ field public static final String WORK_PROFILE_DEFAULT_APPS_TITLE = "PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE";
+ }
+
public final class DevicePolicyStringResource implements android.os.Parcelable {
ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int);
method public int describeContents();
@@ -1193,6 +1234,7 @@
public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
method public boolean canDeviceOwnerGrantSensorsPermissions();
method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getAdminExtras();
method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
method public long getLocalTime();
method @Nullable public java.util.Locale getLocale();
@@ -1206,6 +1248,7 @@
public static final class FullyManagedDeviceProvisioningParams.Builder {
ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+ method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
@@ -1216,6 +1259,7 @@
public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.accounts.Account getAccountToMigrate();
+ method @NonNull public android.os.PersistableBundle getAdminExtras();
method @NonNull public String getOwnerName();
method @NonNull public android.content.ComponentName getProfileAdminComponentName();
method @Nullable public String getProfileName();
@@ -1230,6 +1274,7 @@
ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
+ method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
@@ -1584,6 +1629,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 {
@@ -2019,6 +2157,7 @@
method @Nullable public android.net.Uri getSliceUri();
method @NonNull public String getSmartspaceTargetId();
method @Nullable public String getSourceNotificationKey();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData getTemplateData();
method @NonNull public android.os.UserHandle getUserHandle();
method @Nullable public android.appwidget.AppWidgetProviderInfo getWidget();
method public boolean isSensitive();
@@ -2049,6 +2188,14 @@
field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
field public static final int FEATURE_WEATHER = 1; // 0x1
field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+ field public static final int UI_TEMPLATE_CAROUSEL = 4; // 0x4
+ field public static final int UI_TEMPLATE_COMBINED_CARDS = 6; // 0x6
+ field public static final int UI_TEMPLATE_DEFAULT = 1; // 0x1
+ field public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5; // 0x5
+ field public static final int UI_TEMPLATE_SUB_CARD = 7; // 0x7
+ field public static final int UI_TEMPLATE_SUB_IMAGE = 2; // 0x2
+ field public static final int UI_TEMPLATE_SUB_LIST = 3; // 0x3
+ field public static final int UI_TEMPLATE_UNDEFINED = 0; // 0x0
}
public static final class SmartspaceTarget.Builder {
@@ -2067,6 +2214,7 @@
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setTemplateData(@Nullable android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidget(@NonNull android.appwidget.AppWidgetProviderInfo);
}
@@ -2095,6 +2243,177 @@
}
+package android.app.smartspace.uitemplatedata {
+
+ public final class SmartspaceCarouselUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getCarouselAction();
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> getCarouselItems();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceCarouselUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.CarouselItem implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getImage();
+ method @Nullable public CharSequence getLowerText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getTapAction();
+ method @Nullable public CharSequence getUpperText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> CREATOR;
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.CarouselItem.Builder {
+ ctor public SmartspaceCarouselUiTemplateData.CarouselItem.Builder();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setImage(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setLowerText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setTapAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setUpperText(@Nullable CharSequence);
+ }
+
+ public final class SmartspaceCombinedCardsUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> getCombinedCardDataList();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceCombinedCardsUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceCombinedCardsUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData build();
+ }
+
+ public class SmartspaceDefaultUiTemplateData implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getPrimaryTapAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubTitleIcon();
+ method @Nullable public CharSequence getSubtitleText();
+ method @Nullable public CharSequence getSupplementalAlarmText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSupplementalSubtitleIcon();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSupplementalSubtitleTapAction();
+ method @Nullable public CharSequence getSupplementalSubtitleText();
+ method public int getTemplateType();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getTitleIcon();
+ method @Nullable public CharSequence getTitleText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> CREATOR;
+ }
+
+ public static class SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceDefaultUiTemplateData.Builder(int);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubtitleText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalAlarmText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleText(@NonNull CharSequence);
+ }
+
+ public final class SmartspaceHeadToHeadUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getHeadToHeadAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadFirstCompetitorIcon();
+ method @Nullable public CharSequence getHeadToHeadFirstCompetitorText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadSecondCompetitorIcon();
+ method @Nullable public CharSequence getHeadToHeadSecondCompetitorText();
+ method @Nullable public CharSequence getHeadToHeadTitle();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceHeadToHeadUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceHeadToHeadUiTemplateData.Builder();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadTitle(@Nullable CharSequence);
+ }
+
+ public final class SmartspaceIcon implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public CharSequence getContentDescription();
+ method @NonNull public android.graphics.drawable.Icon getIcon();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceIcon> CREATOR;
+ }
+
+ public static final class SmartspaceIcon.Builder {
+ ctor public SmartspaceIcon.Builder(@NonNull android.graphics.drawable.Icon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon.Builder setContentDescription(@NonNull CharSequence);
+ }
+
+ public final class SmartspaceSubCardUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubCardAction();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubCardIcon();
+ method @Nullable public CharSequence getSubCardText();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubCardUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubCardUiTemplateData.Builder(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public final class SmartspaceSubImageUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubImageAction();
+ method @NonNull public java.util.List<java.lang.CharSequence> getSubImageTexts();
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon> getSubImages();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubImageUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubImageUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>, @NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public final class SmartspaceSubListUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubListAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubListIcon();
+ method @NonNull public java.util.List<java.lang.CharSequence> getSubListTexts();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubListUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubListUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setSubListIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ }
+
+ public final class SmartspaceTapAction implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public CharSequence getId();
+ method @Nullable public android.content.Intent getIntent();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @Nullable public android.os.UserHandle getUserHandle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceTapAction> CREATOR;
+ }
+
+ public static final class SmartspaceTapAction.Builder {
+ ctor public SmartspaceTapAction.Builder(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setIntent(@NonNull android.content.Intent);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+ }
+
+}
+
package android.app.time {
public final class Capabilities {
@@ -2161,6 +2480,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 +2547,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);
@@ -2246,16 +2579,126 @@
}
+package android.app.wallpapereffectsgeneration {
+
+ public final class CameraAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public float[] getAnchorPointInOutputUvSpace();
+ method @NonNull public float[] getAnchorPointInWorldSpace();
+ method public float getCameraOrbitPitchDegrees();
+ method public float getCameraOrbitYawDegrees();
+ method public float getDollyDistanceInWorldSpace();
+ method public float getFrustumFarInWorldSpace();
+ method public float getFrustumNearInWorldSpace();
+ method public float getVerticalFovDegrees();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CameraAttributes> CREATOR;
+ }
+
+ public static final class CameraAttributes.Builder {
+ ctor public CameraAttributes.Builder(@NonNull float[], @NonNull float[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes build();
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitPitchDegrees(@FloatRange(from=-90.0F, to=90.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitYawDegrees(@FloatRange(from=-180.0F, to=180.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setDollyDistanceInWorldSpace(float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumFarInWorldSpace(@FloatRange(from=0.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setFrustumNearInWorldSpace(@FloatRange(from=0.0f) float);
+ method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setVerticalFovDegrees(@FloatRange(from=0.0f, to=180.0f, fromInclusive=false) float);
+ }
+
+ public final class CinematicEffectRequest implements android.os.Parcelable {
+ ctor public CinematicEffectRequest(@NonNull String, @NonNull android.graphics.Bitmap);
+ method public int describeContents();
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method @NonNull public String getTaskId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectRequest> CREATOR;
+ }
+
+ public final class CinematicEffectResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getEndKeyFrame();
+ method public int getImageContentType();
+ method @Nullable public android.app.wallpapereffectsgeneration.CameraAttributes getStartKeyFrame();
+ method public int getStatusCode();
+ method @NonNull public String getTaskId();
+ method @NonNull public java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh> getTexturedMeshes();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2; // 0x2
+ field public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3; // 0x3
+ field public static final int CINEMATIC_EFFECT_STATUS_OK = 1; // 0x1
+ field public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4; // 0x4
+ field public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5; // 0x5
+ field public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectResponse> CREATOR;
+ field public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2; // 0x2
+ field public static final int IMAGE_CONTENT_TYPE_OTHER = 3; // 0x3
+ field public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1; // 0x1
+ field public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class CinematicEffectResponse.Builder {
+ ctor public CinematicEffectResponse.Builder(int, @NonNull String);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse build();
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setEndKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setImageContentType(int);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setStartKeyFrame(@Nullable android.app.wallpapereffectsgeneration.CameraAttributes);
+ method @NonNull public android.app.wallpapereffectsgeneration.CinematicEffectResponse.Builder setTexturedMeshes(@NonNull java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh>);
+ }
+
+ public final class TexturedMesh implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method @NonNull public int[] getIndices();
+ method @NonNull public int getIndicesLayoutType();
+ method @NonNull public float[] getVertices();
+ method @NonNull public int getVerticesLayoutType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.TexturedMesh> CREATOR;
+ field public static final int INDICES_LAYOUT_TRIANGLES = 1; // 0x1
+ field public static final int INDICES_LAYOUT_UNDEFINED = 0; // 0x0
+ field public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1; // 0x1
+ field public static final int VERTICES_LAYOUT_UNDEFINED = 0; // 0x0
+ }
+
+ public static final class TexturedMesh.Builder {
+ ctor public TexturedMesh.Builder(@NonNull android.graphics.Bitmap);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh build();
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndices(@NonNull int[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setIndicesLayoutType(int);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVertices(@NonNull float[]);
+ method @NonNull public android.app.wallpapereffectsgeneration.TexturedMesh.Builder setVerticesLayoutType(int);
+ }
+
+ public final class WallpaperEffectsGenerationManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION) public void generateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager.CinematicEffectListener);
+ }
+
+ public static interface WallpaperEffectsGenerationManager.CinematicEffectListener {
+ method public void onCinematicEffectGenerated(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+ }
+
+}
+
package android.apphibernation {
public class AppHibernationManager {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean);
}
+ public final class HibernationStats implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getDiskBytesSaved();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.apphibernation.HibernationStats> CREATOR;
+ }
+
}
package android.companion {
@@ -2265,18 +2708,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[]);
@@ -2324,6 +2755,8 @@
public final class VirtualDeviceParams implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities();
+ method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities();
method public int getLockState();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -2335,7 +2768,9 @@
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 public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>);
+ 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>);
}
@@ -2392,6 +2827,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
+ method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -2400,8 +2836,9 @@
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String BACKUP_SERVICE = "backup";
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 int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
+ field @Deprecated public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
+ field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
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";
@@ -2431,6 +2868,7 @@
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
field public static final String UWB_SERVICE = "uwb";
field public static final String VR_SERVICE = "vrmanager";
+ field public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE = "wallpaper_effects_generation";
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -2481,7 +2919,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";
@@ -2495,6 +2932,7 @@
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
+ field public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED";
field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES";
field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
@@ -2512,16 +2950,14 @@
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";
field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
+ field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+ field public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 16777216; // 0x1000000
field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
}
@@ -3404,6 +3840,7 @@
method public void sendKeyEvent(int, boolean);
method public void sendVendorCommand(int, byte[], boolean);
method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener);
+ method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener, int);
}
public static interface HdmiClient.OnDeviceSelectedListener {
@@ -5252,9 +5689,9 @@
ctor public LastLocationRequest.Builder();
ctor public LastLocationRequest.Builder(@NonNull android.location.LastLocationRequest);
method @NonNull public android.location.LastLocationRequest build();
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LastLocationRequest.Builder setHiddenFromAppOps(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean);
}
public class Location implements android.os.Parcelable {
@@ -5272,7 +5709,6 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.location.Location getLastKnownLocation(@NonNull String, @NonNull android.location.LastLocationRequest);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections(@NonNull android.location.GnssMeasurementCorrections);
method public boolean isAdasGnssLocationEnabled();
- method @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public boolean isAutoGnssSuspended();
method public boolean isExtraLocationControllerPackageEnabled();
method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle);
method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -5284,8 +5720,7 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAdasGnssLocationEnabled(boolean);
- method @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public void setAutoGnssSuspended(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public void setAdasGnssLocationEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
@@ -5318,7 +5753,7 @@
method @Deprecated @NonNull public android.location.LocationRequest setFastestInterval(long);
method @Deprecated public void setHideFromAppOps(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setInterval(long);
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
+ method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setLowPowerMode(boolean);
method @Deprecated @NonNull public android.location.LocationRequest setNumUpdates(int);
method @Deprecated @NonNull public android.location.LocationRequest setProvider(@NonNull String);
@@ -5334,9 +5769,9 @@
}
public static final class LocationRequest.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
}
@@ -5344,23 +5779,41 @@
public final class SatellitePvt implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.SatellitePvt.ClockInfo getClockInfo();
+ method public int getEphemerisSource();
method @FloatRange public double getIonoDelayMeters();
+ method @IntRange(from=0, to=1023) public int getIssueOfDataClock();
+ method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris();
method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef();
+ method @IntRange(from=0, to=604784) public int getTimeOfClock();
+ method @IntRange(from=0, to=604784) public int getTimeOfEphemeris();
method @FloatRange public double getTropoDelayMeters();
method @Nullable public android.location.SatellitePvt.VelocityEcef getVelocityEcef();
method public boolean hasIono();
+ method public boolean hasIssueOfDataClock();
+ method public boolean hasIssueOfDataEphemeris();
method public boolean hasPositionVelocityClockInfo();
+ method public boolean hasTimeOfClock();
+ method public boolean hasTimeOfEphemeris();
method public boolean hasTropo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.SatellitePvt> CREATOR;
+ field public static final int EPHEMERIS_SOURCE_DEMODULATED = 0; // 0x0
+ field public static final int EPHEMERIS_SOURCE_OTHER = 3; // 0x3
+ field public static final int EPHEMERIS_SOURCE_SERVER_LONG_TERM = 2; // 0x2
+ field public static final int EPHEMERIS_SOURCE_SERVER_NORMAL = 1; // 0x1
}
public static final class SatellitePvt.Builder {
ctor public SatellitePvt.Builder();
method @NonNull public android.location.SatellitePvt build();
method @NonNull public android.location.SatellitePvt.Builder setClockInfo(@NonNull android.location.SatellitePvt.ClockInfo);
+ method @NonNull public android.location.SatellitePvt.Builder setEphemerisSource(int);
method @NonNull public android.location.SatellitePvt.Builder setIonoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
+ method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int);
+ method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int);
method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef);
+ method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0, to=604784) int);
+ method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0, to=604784) int);
method @NonNull public android.location.SatellitePvt.Builder setTropoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
method @NonNull public android.location.SatellitePvt.Builder setVelocityEcef(@NonNull android.location.SatellitePvt.VelocityEcef);
}
@@ -5482,11 +5935,20 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
}
+ public class AudioDescriptor implements android.os.Parcelable {
+ ctor public AudioDescriptor(int, int, @NonNull byte[]);
+ }
+
public final class AudioDeviceAttributes implements android.os.Parcelable {
ctor public AudioDeviceAttributes(@NonNull android.media.AudioDeviceInfo);
ctor public AudioDeviceAttributes(int, int, @NonNull String);
+ ctor public AudioDeviceAttributes(int, int, @NonNull String, @NonNull String, @NonNull java.util.List<android.media.AudioProfile>, @NonNull java.util.List<android.media.AudioDescriptor>);
method public int describeContents();
+ method public boolean equalTypeAddress(@Nullable Object);
method @NonNull public String getAddress();
+ method @NonNull public java.util.List<android.media.AudioDescriptor> getAudioDescriptors();
+ method @NonNull public java.util.List<android.media.AudioProfile> getAudioProfiles();
+ method @NonNull public String getName();
method public int getRole();
method public int getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5545,6 +6007,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);
@@ -5569,6 +6032,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
+ field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
@@ -5577,7 +6041,11 @@
field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2
field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1
field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0
+ field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
+ field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
+ field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
+ field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
field public static final int SUCCESS = 0; // 0x0
}
@@ -5637,6 +6105,10 @@
field public static final int PLAYER_TYPE_UNKNOWN = -1; // 0xffffffff
}
+ public class AudioProfile implements android.os.Parcelable {
+ ctor public AudioProfile(int, @NonNull int[], @NonNull int[], @NonNull int[], int);
+ }
+
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
method public static long getMaxSharedAudioHistoryMillis();
@@ -6177,6 +6649,7 @@
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+ method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
method public int getClientPriority(int, @Nullable String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
@@ -6349,7 +6822,7 @@
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
- method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]);
+ method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
method public boolean isLowestPriority(int);
@@ -7502,7 +7975,7 @@
public class FrontendStatus {
method public int getAgc();
- method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpInfo[] getAllAtsc3PlpInfo();
+ method @NonNull public java.util.List<android.media.tv.tuner.frontend.Atsc3PlpInfo> getAllAtsc3PlpInfo();
method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo[] getAtsc3PlpTuningInfo();
method public int getBandwidth();
method public int getBer();
@@ -7594,7 +8067,7 @@
method public boolean isLocked();
}
- public class FrontendStatusReadiness {
+ public final class FrontendStatusReadiness {
method public int getStatusReadiness();
method public int getStatusType();
field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3
@@ -7886,23 +8359,6 @@
method public void release();
}
- public final class IpSecManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
- method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- }
-
- public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable {
- method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void addAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
- method public void close();
- method @NonNull public String getInterfaceName();
- method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
- }
-
- public static class IpSecTransform.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- }
-
public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public MatchAllNetworkSpecifier();
method public int describeContents();
@@ -8618,6 +9074,8 @@
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int);
@@ -8683,6 +9141,7 @@
}
public static class Build.VERSION {
+ field @NonNull public static final java.util.Set<java.lang.String> KNOWN_CODENAMES;
field @NonNull public static final String PREVIEW_SDK_FINGERPRINT;
}
@@ -9000,6 +9459,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
@@ -9014,11 +9474,14 @@
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable();
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public boolean isLowPowerStandbySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyActiveDuringMaintenance(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
@@ -9211,6 +9674,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);
+ 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();
@@ -9505,7 +9970,8 @@
method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
method @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, long, int, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String);
field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS";
field public static final int PERMISSION_GRANTED = 0; // 0x0
@@ -9913,6 +10379,7 @@
method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean);
field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
+ field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
@@ -9953,6 +10420,8 @@
}
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+ method public static int getIntForUser(@NonNull android.content.ContentResolver, @NonNull String, int, int);
+ method @Nullable public static String getStringForUser(@NonNull android.content.ContentResolver, @NonNull String, int);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
field @Deprecated public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = "accessibility_display_magnification_navbar_enabled";
@@ -10304,6 +10773,8 @@
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback);
method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback);
+ method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback);
+ method public void onStopProximityUpdates();
field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6
field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3
field public static final int ATTENTION_FAILURE_PREEMPTED = 4; // 0x4
@@ -10311,6 +10782,7 @@
field public static final int ATTENTION_FAILURE_UNKNOWN = 2; // 0x2
field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0
field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1
+ field public static final double PROXIMITY_UNKNOWN = -1.0;
field public static final String SERVICE_INTERFACE = "android.service.attention.AttentionService";
}
@@ -10319,6 +10791,10 @@
method public void onSuccess(int, long);
}
+ public static final class AttentionService.ProximityCallback {
+ method public void onProximityUpdate(double);
+ }
+
}
package android.service.autofill {
@@ -10337,9 +10813,9 @@
}
public static final class Dataset.Builder {
- ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+ ctor @Deprecated public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.Dataset.Builder setContent(@NonNull android.view.autofill.AutofillId, @Nullable android.content.ClipData);
- method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+ method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
}
public abstract class InlineSuggestionRenderService extends android.app.Service {
@@ -10442,6 +10918,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 {
@@ -10717,7 +11204,7 @@
public class GameService extends android.app.Service {
ctor public GameService();
- method public final void createGameSession(@IntRange(from=0) int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from=0) int);
method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
method public void onConnected();
method public void onDisconnected();
@@ -10731,8 +11218,10 @@
method public void onCreate();
method public void onDestroy();
method public void onGameTaskFocusChanged(boolean);
- method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame();
+ method public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame();
method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void startActivityFromGameSessionForResult(@NonNull android.content.Intent, @Nullable android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSessionActivityCallback);
method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback);
}
@@ -10742,6 +11231,11 @@
field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0
}
+ public interface GameSessionActivityCallback {
+ method public void onActivityResult(int, @Nullable android.content.Intent);
+ method public default void onActivityStartFailed(@NonNull Throwable);
+ }
+
public abstract class GameSessionService extends android.app.Service {
ctor public GameSessionService();
method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
@@ -10880,7 +11374,7 @@
method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
method public long getMaximumDataBlockSize();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
- method @NonNull public String getPersistentDataPackageName();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
method public byte[] read();
method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
@@ -11118,6 +11612,22 @@
}
+package android.service.tracing {
+
+ public class TraceReportService extends android.app.Service {
+ ctor public TraceReportService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public boolean onMessage(@NonNull android.os.Message);
+ method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
+ }
+
+ public static final class TraceReportService.TraceParams {
+ method @NonNull public android.os.ParcelFileDescriptor getFd();
+ method @NonNull public java.util.UUID getUuid();
+ }
+
+}
+
package android.service.translation {
public abstract class TranslationService extends android.app.Service {
@@ -11144,6 +11654,7 @@
method @Deprecated public final void grantTrust(CharSequence, long, boolean);
method public final void grantTrust(CharSequence, long, int);
method public final void isEscrowTokenActive(long, android.os.UserHandle);
+ method public final void lockUser();
method public final android.os.IBinder onBind(android.content.Intent);
method public boolean onConfigure(java.util.List<android.os.PersistableBundle>);
method public void onDeviceLocked();
@@ -11154,13 +11665,16 @@
method public void onEscrowTokenStateReceived(long, int);
method public void onTrustTimeout();
method public void onUnlockAttempt(boolean);
+ method public void onUserRequestedUnlock();
method public final void removeEscrowToken(long, android.os.UserHandle);
method public final void revokeTrust();
method public final void setManagingTrust(boolean);
method public final void showKeyguardErrorMessage(@NonNull CharSequence);
method public final void unlockUserWithToken(long, byte[], android.os.UserHandle);
field public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 2; // 0x2
+ field public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 8; // 0x8
field public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1; // 0x1
+ field public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 4; // 0x4
field public static final String SERVICE_INTERFACE = "android.service.trust.TrustAgentService";
field public static final int TOKEN_STATE_ACTIVE = 1; // 0x1
field public static final int TOKEN_STATE_INACTIVE = 0; // 0x0
@@ -11336,6 +11850,17 @@
}
+package android.service.wallpapereffectsgeneration {
+
+ public abstract class WallpaperEffectsGenerationService extends android.app.Service {
+ ctor public WallpaperEffectsGenerationService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onGenerateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest);
+ method public final void returnCinematicEffectResponse(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+ }
+
+}
+
package android.service.watchdog {
public abstract class ExplicitHealthCheckService extends android.app.Service {
@@ -11510,6 +12035,7 @@
public abstract class ConnectionService extends android.app.Service {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
}
public abstract class InCallService extends android.app.Service {
@@ -11738,6 +12264,7 @@
field public static final int CALL_SOURCE_EMERGENCY_SHORTCUT = 2; // 0x2
field public static final int CALL_SOURCE_UNSPECIFIED = 0; // 0x0
field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
+ field public static final String EXTRA_CALL_HAS_IN_BAND_RINGTONE = "android.telecom.extra.CALL_HAS_IN_BAND_RINGTONE";
field public static final String EXTRA_CALL_SOURCE = "android.telecom.extra.CALL_SOURCE";
field public static final String EXTRA_CALL_TECHNOLOGY_TYPE = "android.telecom.extra.CALL_TECHNOLOGY_TYPE";
field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
@@ -12681,7 +13208,6 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypesBitmask();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypesForReason(int);
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -12727,7 +13253,6 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Collection<android.telephony.UiccSlotMapping> getSimSlotMapping();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.UiccSlotInfo[] getUiccSlotsInfo();
@@ -12782,7 +13307,6 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int sendThermalMitigationRequest(@NonNull android.telephony.ThermalMitigationRequest);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAllowedNetworkTypesForReason(int, long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
@@ -12832,10 +13356,8 @@
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
- field public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2; // 0x2
field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
- field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0
field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
@@ -12875,26 +13397,6 @@
field public static final int KEY_TYPE_WLAN = 2; // 0x2
field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
- field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
- field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
- field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
- field public static final long NETWORK_TYPE_BITMASK_EHRPD = 8192L; // 0x2000L
- field public static final long NETWORK_TYPE_BITMASK_EVDO_0 = 16L; // 0x10L
- field public static final long NETWORK_TYPE_BITMASK_EVDO_A = 32L; // 0x20L
- field public static final long NETWORK_TYPE_BITMASK_EVDO_B = 2048L; // 0x800L
- field public static final long NETWORK_TYPE_BITMASK_GPRS = 1L; // 0x1L
- field public static final long NETWORK_TYPE_BITMASK_GSM = 32768L; // 0x8000L
- field public static final long NETWORK_TYPE_BITMASK_HSDPA = 128L; // 0x80L
- field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
- field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
- field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
- field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
- field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
- field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
- field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
- field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
- field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
- field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
@@ -12951,6 +13453,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
@@ -14220,6 +14723,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR;
field public static final String RCS_PROFILE_1_0 = "UP_1.0";
field public static final String RCS_PROFILE_2_3 = "UP_2.3";
+ field public static final String RCS_PROFILE_2_4 = "UP_2.4";
}
public final class RcsContactPresenceTuple implements android.os.Parcelable {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 3d756ba..1b45e88 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1,34 +1,26 @@
// Baseline format: 1.0
ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
-
-
-BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
-
-
ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
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 +38,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 +92,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 +110,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 +220,77 @@
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:
+
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 169346b..e27fc34 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";
@@ -103,6 +104,8 @@
package android.app {
@UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+ method public final boolean addDumpable(@NonNull android.util.Dumpable);
+ method public void dumpInternal(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
method public void onMovedToDisplay(int, android.content.res.Configuration);
}
@@ -150,7 +153,7 @@
public class ActivityOptions {
method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
- method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+ method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
@@ -274,6 +277,11 @@
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
}
+ public final class GameManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public boolean isAngleEnabled(@NonNull String);
+ method public void setGameServiceProvider(@Nullable String);
+ }
+
public abstract class HomeVisibilityListener {
ctor public HomeVisibilityListener();
method public abstract void onHomeVisibilityChanged(boolean);
@@ -348,6 +356,32 @@
ctor public PictureInPictureUiState(boolean);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method @NonNull public static String createPropertyName(int, @NonNull String);
+ method public final void disableForCurrentProcess();
+ method public static void disableForTestMode();
+ method public final void disableInstance();
+ method public final void disableSystemWide();
+ method public final void forgetDisableLocal();
+ method public boolean getDisabledState();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method public final boolean isDisabled();
+ method @Nullable public final Result query(@NonNull Query);
+ method public static void setTestMode(boolean);
+ method public void testPropertyName();
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ field public static final int MODULE_SYSTEM = 1; // 0x1
+ field public static final int MODULE_TEST = 0; // 0x0
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method public void cancelRequestAddTile(@NonNull String);
method public void clickNotification(@Nullable String, int, int, boolean);
@@ -455,6 +489,7 @@
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
method public void forceUpdateUserSetupComplete(int);
method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
+ method public int getDeviceOwnerType(@NonNull android.content.ComponentName);
method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
method public long getLastBugReportRequestTime();
method public long getLastNetworkLogRetrievalTime();
@@ -463,6 +498,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
@@ -470,10 +506,13 @@
method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+ method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
+ field public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; // 0x0
+ field public static final int DEVICE_OWNER_TYPE_FINANCED = 1; // 0x1
field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -765,6 +804,7 @@
method @NonNull public String getPermissionControllerPackageName();
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+ method @NonNull public String getSupplementalProcessPackageName();
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
method public void holdLock(android.os.IBinder, int);
@@ -947,7 +987,7 @@
package android.graphics {
public final class ImageDecoder implements java.lang.AutoCloseable {
- method @AnyThread @NonNull public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, java.io.InputStream, int);
+ method @AnyThread @NonNull public static android.graphics.ImageDecoder.Source createSource(android.content.res.Resources, @NonNull java.io.InputStream, int);
}
public final class Rect implements android.os.Parcelable {
@@ -1085,7 +1125,7 @@
package android.hardware.devicestate {
public final class DeviceStateManager {
- method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
@@ -1569,10 +1609,6 @@
method public void setIncludeTestInterfaces(boolean);
}
- public final class IpSecManager {
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
- }
-
public class NetworkPolicyManager {
method public boolean getRestrictBackground();
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
@@ -1703,8 +1739,11 @@
public final class Parcel {
method public boolean allowSquashing();
+ method public int getFlags();
method public int readExceptionCode();
method public void restoreAllowSquashing(boolean);
+ field public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1; // 0x1
+ field public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 2; // 0x2
}
public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -1712,7 +1751,9 @@
}
public final class PowerManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
}
public class Process {
@@ -2479,7 +2520,6 @@
method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
@@ -2701,6 +2741,7 @@
method @NonNull public android.view.Display.Mode getDefaultMode();
method @NonNull public int[] getReportedHdrTypes();
method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
+ method @Nullable public android.view.Display.Mode getSystemPreferredDisplayMode();
method public int getType();
method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
method public boolean hasAccess(int);
@@ -2776,7 +2817,7 @@
method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams);
}
- @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
+ @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
method public android.view.View getTooltipView();
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 897bab4..edf9ece 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -29,10 +29,20 @@
}
filegroup {
+ name: "IBinaryTransparencyService.aidl",
+ srcs: ["com/android/internal/os/IBinaryTransparencyService.aidl"],
+}
+
+filegroup {
name: "ITracingServiceProxy.aidl",
srcs: ["android/tracing/ITracingServiceProxy.aidl"],
}
+filegroup {
+ name: "TraceReportParams.aidl",
+ srcs: ["android/tracing/TraceReportParams.aidl"],
+}
+
// These are subset of framework-core-sources that are needed by the
// android.test.mock library. The implementation of android.test.mock references
// private members of various components to allow mocking of classes that cannot
@@ -118,6 +128,7 @@
"android/os/IThermalStatusListener.aidl",
"android/os/IThermalService.aidl",
"android/os/IPowerManager.aidl",
+ "android/os/IWakeLockCallback.aidl",
],
}
@@ -165,6 +176,18 @@
"com/android/internal/util/IndentingPrintWriter.java",
"com/android/internal/util/MessageUtils.java",
"com/android/internal/util/WakeupMessage.java",
+ // TODO: delete as soon as NetworkStatsFactory stops using
+ "com/android/internal/util/ProcFileReader.java",
+ ],
+}
+
+// keep these files in sync with the packages/modules/Connectivity jarjar-rules.txt for
+// the connectivity module.
+filegroup {
+ name: "framework-connectivity-api-shared-srcs",
+ srcs: [
+ "android/util/IndentingPrintWriter.java",
+ "com/android/internal/util/FileRotator.java",
],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 479e6bf..50473f1 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -571,6 +571,31 @@
*/
public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+ /**
+ * Action to trigger dpad up keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_UP = 16;
+
+ /**
+ * Action to trigger dpad down keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_DOWN = 17;
+
+ /**
+ * Action to trigger dpad left keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_LEFT = 18;
+
+ /**
+ * Action to trigger dpad right keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_RIGHT = 19;
+
+ /**
+ * Action to trigger dpad center keyevent.
+ */
+ public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+
private static final String LOG_TAG = "AccessibilityService";
/**
@@ -1400,7 +1425,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 +1462,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 +1499,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 +1540,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 +1710,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 +1752,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 +1791,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);
@@ -2312,10 +2353,16 @@
* @param action The action to perform.
* @return Whether the action was successfully performed.
*
+ * Perform actions using ids like the id constants referenced below:
* @see #GLOBAL_ACTION_BACK
* @see #GLOBAL_ACTION_HOME
* @see #GLOBAL_ACTION_NOTIFICATIONS
* @see #GLOBAL_ACTION_RECENTS
+ * @see #GLOBAL_ACTION_DPAD_UP
+ * @see #GLOBAL_ACTION_DPAD_DOWN
+ * @see #GLOBAL_ACTION_DPAD_LEFT
+ * @see #GLOBAL_ACTION_DPAD_RIGHT
+ * @see #GLOBAL_ACTION_DPAD_CENTER
*/
public final boolean performGlobalAction(int action) {
IAccessibilityServiceConnection connection =
@@ -3073,6 +3120,33 @@
}
}
+ /**
+ * Sets the system settings values that control the scaling factor for animations. The scale
+ * controls the animation playback speed for animations that respect these settings. Animations
+ * that do not respect the settings values will not be affected by this function. A lower scale
+ * value results in a faster speed. A value of <code>0</code> disables animations entirely. When
+ * animations are disabled services receive window change events more quickly which can reduce
+ * the potential by confusion by reducing the time during which windows are in transition.
+ *
+ * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE
+ * @param scale The scaling factor for all animations.
+ */
+ public void setAnimationScale(float scale) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setAnimationScale(scale);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
private static class AccessibilityContext extends ContextWrapper {
private final int mConnectionId;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 2cc15b4..0d6b199 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -144,4 +144,6 @@
void onDoubleTap(int displayId);
void onDoubleTapAndHold(int displayId);
+
+ void setAnimationScale(float scale);
}
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index bb2b8d4..735df80 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -376,7 +376,7 @@
throw new IllegalStateException(
"State transitions are not allowed without first adding a callback.");
}
- if (mState != STATE_TOUCH_INTERACTING) {
+ if (mState != STATE_TOUCH_INTERACTING && mState != STATE_DRAGGING) {
throw new IllegalStateException(
"State transitions are not allowed in " + stateToString(mState));
}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index eb525d3..a8ff36a 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
@@ -535,7 +536,7 @@
* @param animation The started animation.
* @param isReverse Whether the animation is playing in reverse.
*/
- default void onAnimationStart(Animator animation, boolean isReverse) {
+ default void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
onAnimationStart(animation);
}
@@ -551,7 +552,7 @@
* @param animation The animation which reached its end.
* @param isReverse Whether the animation is playing in reverse.
*/
- default void onAnimationEnd(Animator animation, boolean isReverse) {
+ default void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
onAnimationEnd(animation);
}
@@ -560,7 +561,7 @@
*
* @param animation The started animation.
*/
- void onAnimationStart(Animator animation);
+ void onAnimationStart(@NonNull Animator animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
@@ -568,7 +569,7 @@
*
* @param animation The animation which reached its end.
*/
- void onAnimationEnd(Animator animation);
+ void onAnimationEnd(@NonNull Animator animation);
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
@@ -576,14 +577,14 @@
*
* @param animation The animation which was canceled.
*/
- void onAnimationCancel(Animator animation);
+ void onAnimationCancel(@NonNull Animator animation);
/**
* <p>Notifies the repetition of the animation.</p>
*
* @param animation The animation which was repeated.
*/
- void onAnimationRepeat(Animator animation);
+ void onAnimationRepeat(@NonNull Animator animation);
}
/**
@@ -599,7 +600,7 @@
* @param animation The animaton being paused.
* @see #pause()
*/
- void onAnimationPause(Animator animation);
+ void onAnimationPause(@NonNull Animator animation);
/**
* <p>Notifies that the animation was resumed, after being
@@ -608,7 +609,7 @@
* @param animation The animation being resumed.
* @see #resume()
*/
- void onAnimationResume(Animator animation);
+ void onAnimationResume(@NonNull Animator animation);
}
/**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 3cbae99..06b424b 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -18,6 +18,7 @@
import android.annotation.CallSuper;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1626,7 +1627,7 @@
*
* @param animation The animation which was repeated.
*/
- void onAnimationUpdate(ValueAnimator animation);
+ void onAnimationUpdate(@NonNull ValueAnimator animation);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a7b96a6..8935022 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -33,12 +33,16 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.VoiceInteractor.Request;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -788,6 +792,16 @@
private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+ /**
+ * After {@link Build.VERSION_CODES#TIRAMISU},
+ * {@link #dump(String, FileDescriptor, PrintWriter, String[])} is not called if
+ * {@code dumpsys activity} is called with some special arguments.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ @VisibleForTesting
+ private static final long DUMP_IGNORES_SPECIAL_ARGS = 149254050L;
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -5489,6 +5503,17 @@
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
+ startActivityAsCaller(intent, options, permissionToken, ignoreTargetSecurity, userId, -1);
+ }
+
+ /**
+ * @see #startActivityAsCaller(Intent, Bundle, IBinder, boolean, int)
+ * @param requestCode The request code used for returning a result or -1 if no result should be
+ * returned.
+ * @hide
+ */
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId, int requestCode) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -5496,11 +5521,11 @@
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
+ intent, requestCode, options, permissionToken, ignoreTargetSecurity,
+ userId);
if (ar != null) {
mMainThread.sendActivityResult(
- mToken, mEmbeddedID, -1, ar.getResultCode(),
- ar.getResultData());
+ mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
}
cancelInputsAndStartExitTransition(options);
}
@@ -6125,14 +6150,41 @@
* you to specify a custom animation even when starting an activity from
* outside the context of the current top activity.
*
+ * <p>Af of {@link android.os.Build.VERSION_CODES#S} application can only specify
+ * a transition animation when the transition happens within the same task. System
+ * default animation is used for cross-task transition animations.
+ *
* @param enterAnim A resource ID of the animation resource to use for
* the incoming activity. Use 0 for no animation.
* @param exitAnim A resource ID of the animation resource to use for
* the outgoing activity. Use 0 for no animation.
*/
public void overridePendingTransition(int enterAnim, int exitAnim) {
- ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(),
- enterAnim, exitAnim);
+ overridePendingTransition(enterAnim, exitAnim, 0);
+ }
+
+ /**
+ * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+ * or {@link #finish} to specify an explicit transition animation to
+ * perform next.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+ * to using this with starting activities is to supply the desired animation
+ * information through a {@link ActivityOptions} bundle to
+ * {@link #startActivity(Intent, Bundle)} or a related function. This allows
+ * you to specify a custom animation even when starting an activity from
+ * outside the context of the current top activity.
+ *
+ * @param enterAnim A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitAnim A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ */
+ public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) {
+ ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim,
+ exitAnim, backgroundColor);
}
/**
@@ -7079,7 +7131,18 @@
/**
* Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
+ * you run <code>adb shell dumpsys activity <activity_component_name></code>.
+ *
+ * <p>This method won't be called if the app targets
+ * {@link android.os.Build.VERSION_CODES#TIRAMISU} or later if the dump request starts with one
+ * of the following arguments:
+ * <ul>
+ * <li>--autofill
+ * <li>--contentcapture
+ * <li>--translation
+ * <li>--list-dumpables
+ * <li>--dump-dumpable
+ * </ul>
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
@@ -7098,6 +7161,7 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public final boolean addDumpable(@NonNull Dumpable dumpable) {
if (mDumpableContainer == null) {
mDumpableContainer = new DumpableContainerImpl();
@@ -7105,11 +7169,20 @@
return mDumpableContainer.addDumpable(dumpable);
}
- void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+ /**
+ * This is the real method called by {@code ActivityThread}, but it's also exposed so
+ * CTS can test for the special args cases.
+ *
+ * @hide
+ */
+ @TestApi
+ @VisibleForTesting
+ @SuppressLint("OnNameExpected")
+ public void dumpInternal(@NonNull String prefix,
+ @SuppressLint("UseParcelFileDescriptor") @Nullable FileDescriptor fd,
@NonNull PrintWriter writer, @Nullable String[] args) {
- String innerPrefix = prefix + " ";
-
- if (args != null && args.length > 0) {
+ if (args != null && args.length > 0
+ && CompatChanges.isChangeEnabled(DUMP_IGNORES_SPECIAL_ARGS)) {
// Handle special cases
switch (args[0]) {
case "--autofill":
@@ -7144,6 +7217,12 @@
return;
}
}
+ dump(prefix, fd, writer, args);
+ }
+
+ void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ String innerPrefix = prefix + " ";
writer.print(prefix); writer.print("Local Activity ");
writer.print(Integer.toHexString(System.identityHashCode(this)));
@@ -7160,11 +7239,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);
@@ -8683,17 +8757,15 @@
* Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this
* activity is attached to.
*
- * Returns null if the activity is not attached to a window with a decor.
+ * @throws IllegalStateException if this Activity is not visual.
*/
- @Nullable
+ @NonNull
@Override
public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
- if (mWindow != null) {
- View decorView = mWindow.getDecorView();
- if (decorView != null) {
- return decorView.getOnBackInvokedDispatcher();
- }
+ if (mWindow == null) {
+ throw new IllegalStateException("OnBackInvokedDispatcher are not available on "
+ + "non-visual activities");
}
- return null;
+ return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher();
}
}
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index eb4a355..605a1fa 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -428,11 +428,11 @@
}
}
- void overridePendingTransition(IBinder token, String packageName,
- int enterAnim, int exitAnim) {
+ void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim,
+ int backgroundColor) {
try {
getActivityClientController().overridePendingTransition(token, packageName,
- enterAnim, exitAnim);
+ enterAnim, exitAnim, backgroundColor);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
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..a58ceaa 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;
@@ -214,6 +215,14 @@
public abstract boolean isSystemReady();
/**
+ * Returns package name given pid.
+ *
+ * @param pid The pid we are searching package name for.
+ */
+ @Nullable
+ public abstract String getPackageNameByPid(int pid);
+
+ /**
* Sets if the given pid has an overlay UI or not.
*
* @param pid The pid we are setting overlay UI for.
@@ -528,9 +537,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 +723,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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5e5649f..e405b60 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -126,6 +126,12 @@
public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
/**
+ * Custom background color for animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_BACKGROUND_COLOR = "android:activity.backgroundColor";
+
+ /**
* Bitmap for thumbnail animation.
* @hide
*/
@@ -389,6 +395,7 @@
private int mCustomEnterResId;
private int mCustomExitResId;
private int mCustomInPlaceResId;
+ private int mCustomBackgroundColor;
private Bitmap mThumbnail;
private int mStartX;
private int mStartY;
@@ -453,7 +460,27 @@
*/
public static ActivityOptions makeCustomAnimation(Context context,
int enterResId, int exitResId) {
- return makeCustomAnimation(context, enterResId, exitResId, null, null, null);
+ return makeCustomAnimation(context, enterResId, exitResId, 0, null, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying a custom animation to run when
+ * the activity is displayed.
+ *
+ * @param context Who is defining this. This is the application that the
+ * animation resources will be loaded from.
+ * @param enterResId A resource ID of the animation resource to use for
+ * the incoming activity. Use 0 for no animation.
+ * @param exitResId A resource ID of the animation resource to use for
+ * the outgoing activity. Use 0 for no animation.
+ * @param backgroundColor The background color to use for the background during the animation if
+ * the animation requires a background. Set to 0 to not override the default color.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
+ int enterResId, int exitResId, int backgroundColor) {
+ return makeCustomAnimation(context, enterResId, exitResId, backgroundColor, null, null);
}
/**
@@ -477,12 +504,14 @@
*/
@UnsupportedAppUsage
public static ActivityOptions makeCustomAnimation(Context context,
- int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
+ int enterResId, int exitResId, int backgroundColor, Handler handler,
+ OnAnimationStartedListener listener) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = context.getPackageName();
opts.mAnimationType = ANIM_CUSTOM;
opts.mCustomEnterResId = enterResId;
opts.mCustomExitResId = exitResId;
+ opts.mCustomBackgroundColor = backgroundColor;
opts.setOnAnimationStartedListener(handler, listener);
return opts;
}
@@ -510,11 +539,11 @@
*/
@TestApi
public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context,
- int enterResId, int exitResId, @Nullable Handler handler,
+ int enterResId, int exitResId, int backgroundColor, @Nullable Handler handler,
@Nullable OnAnimationStartedListener startedListener,
@Nullable OnAnimationFinishedListener finishedListener) {
- ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
- startedListener);
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, backgroundColor,
+ handler, startedListener);
opts.setOnAnimationFinishedListener(handler, finishedListener);
return opts;
}
@@ -547,8 +576,8 @@
int enterResId, int exitResId, @Nullable Handler handler,
@Nullable OnAnimationStartedListener startedListener,
@Nullable OnAnimationFinishedListener finishedListener) {
- ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
- startedListener, finishedListener);
+ ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, 0,
+ handler, startedListener, finishedListener);
opts.mOverrideTaskTransition = true;
return opts;
}
@@ -1243,6 +1272,11 @@
return mCustomInPlaceResId;
}
+ /** @hide */
+ public int getCustomBackgroundColor() {
+ return mCustomBackgroundColor;
+ }
+
/**
* The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so
* it should always be backed by a HardwareBuffer on the other end.
@@ -1775,6 +1809,7 @@
case ANIM_CUSTOM:
mCustomEnterResId = otherOptions.mCustomEnterResId;
mCustomExitResId = otherOptions.mCustomExitResId;
+ mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
mThumbnail = null;
if (mAnimationStartedListener != null) {
try {
@@ -1862,6 +1897,7 @@
case ANIM_CUSTOM:
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+ b.putInt(KEY_ANIM_BACKGROUND_COLOR, mCustomBackgroundColor);
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ea62714..3289304 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;
@@ -201,6 +200,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SomeArgs;
@@ -598,12 +598,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 +619,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 +639,6 @@
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
mActivityOptions = activityOptions;
- mPendingFixedRotationAdjustments = fixedRotationAdjustments;
mLaunchedFromBubble = launchedFromBubble;
init();
}
@@ -1013,6 +1005,11 @@
RemoteCallback finishCallback;
}
+ static final class DumpResourcesData {
+ public ParcelFileDescriptor fd;
+ public RemoteCallback finishCallback;
+ }
+
static final class UpdateCompatibilityData {
String pkg;
CompatibilityInfo info;
@@ -1324,6 +1321,20 @@
sendMessage(H.SCHEDULE_CRASH, args, typeId);
}
+ @Override
+ public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
+ DumpResourcesData data = new DumpResourcesData();
+ try {
+ data.fd = fd.dup();
+ data.finishCallback = callback;
+ sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
+ } catch (IOException e) {
+ Slog.w(TAG, "dumpResources failed", e);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
String prefix, String[] args) {
DumpComponentInfo data = new DumpComponentInfo();
@@ -2047,6 +2058,7 @@
public static final int UPDATE_UI_TRANSLATION_STATE = 163;
public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
public static final int DUMP_GFXINFO = 165;
+ public static final int DUMP_RESOURCES = 166;
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2100,6 +2112,7 @@
case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+ case DUMP_RESOURCES: return "DUMP_RESOURCES";
}
}
return Integer.toString(code);
@@ -2215,6 +2228,9 @@
case DUMP_HEAP:
handleDumpHeap((DumpHeapData) msg.obj);
break;
+ case DUMP_RESOURCES:
+ handleDumpResources((DumpResourcesData) msg.obj);
+ break;
case DUMP_ACTIVITY:
handleDumpActivity((DumpComponentInfo)msg.obj);
break;
@@ -3512,21 +3528,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 +3537,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 +3754,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 +3789,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);
@@ -4686,6 +4609,23 @@
}
}
+ private void handleDumpResources(DumpResourcesData info) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+ info.fd.getFileDescriptor()));
+
+ Resources.dumpHistory(pw, "");
+ pw.flush();
+ if (info.finishCallback != null) {
+ info.finishCallback.sendResult(null);
+ }
+ } finally {
+ IoUtils.closeQuietly(info.fd);
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
private void handleDumpActivity(DumpComponentInfo info) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
@@ -4693,7 +4633,7 @@
if (r != null && r.activity != null) {
PrintWriter pw = new FastPrintWriter(new FileOutputStream(
info.fd.getFileDescriptor()));
- r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+ r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
pw.flush();
}
} finally {
@@ -5974,13 +5914,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 +7576,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());
@@ -7922,6 +7854,8 @@
MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
+ BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
+ BinderCallsStats.startForBluetooth(context); });
}
private void purgePendingResources() {
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0081234..0d1bc05 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1327,9 +1327,17 @@
*/
public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+ /**
+ * Access restricted settings.
+ *
+ * @hide
+ */
+ public static final int OP_ACCESS_RESTRICTED_SETTINGS =
+ AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 119;
+ public static final int _NUM_OP = 120;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1784,6 +1792,14 @@
@SystemApi
public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
+ /**
+ * Limit user accessing restricted settings.
+ *
+ * @hide
+ */
+ public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
+ "android:access_restricted_settings";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2004,6 +2020,7 @@
OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES
OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE
OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER
+ OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS
};
/**
@@ -2129,6 +2146,7 @@
OPSTR_NEARBY_WIFI_DEVICES,
OPSTR_ESTABLISH_VPN_SERVICE,
OPSTR_ESTABLISH_VPN_MANAGER,
+ OPSTR_ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2255,6 +2273,7 @@
"NEARBY_WIFI_DEVICES",
"ESTABLISH_VPN_SERVICE",
"ESTABLISH_VPN_MANAGER",
+ "ACCESS_RESTRICTED_SETTINGS",
};
/**
@@ -2382,6 +2401,7 @@
Manifest.permission.NEARBY_WIFI_DEVICES,
null, // no permission for OP_ESTABLISH_VPN_SERVICE
null, // no permission for OP_ESTABLISH_VPN_MANAGER
+ null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2509,6 +2529,7 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
+ null, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2635,6 +2656,7 @@
null, // NEARBY_WIFI_DEVICES
null, // ESTABLISH_VPN_SERVICE
null, // ESTABLISH_VPN_MANAGER
+ null, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2760,6 +2782,7 @@
AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
+ AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
};
/**
@@ -2889,6 +2912,7 @@
false, // NEARBY_WIFI_DEVICES
false, // OP_ESTABLISH_VPN_SERVICE
false, // OP_ESTABLISH_VPN_MANAGER
+ true, // ACCESS_RESTRICTED_SETTINGS
};
/**
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 9039bbd..60e22f4 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -156,6 +156,12 @@
public static final int REASON_OTHER = 13;
/**
+ * Application process was killed by App Freezer, for example, because it receives
+ * sync binder transactions while being frozen.
+ */
+ public static final int REASON_FREEZER = 14;
+
+ /**
* Application process kills subreason is unknown.
*
* For internal use only.
@@ -487,6 +493,7 @@
REASON_USER_STOPPED,
REASON_DEPENDENCY_DIED,
REASON_OTHER,
+ REASON_FREEZER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -1138,6 +1145,8 @@
return "DEPENDENCY DIED";
case REASON_OTHER:
return "OTHER KILLS BY SYSTEM";
+ case REASON_FREEZER:
+ return "FREEZER";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7b55b6c..20ffa25 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,11 @@
package android.app;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
@@ -31,6 +36,7 @@
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -62,7 +68,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
@@ -74,7 +79,6 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -122,7 +126,6 @@
import libcore.util.EmptyArray;
-import java.io.File;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -172,6 +175,8 @@
private PackageInstaller mInstaller;
@GuardedBy("mLock")
private ArtManager mArtManager;
+ @GuardedBy("mLock")
+ private DevicePolicyManager mDevicePolicyManager;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -188,6 +193,15 @@
}
}
+ DevicePolicyManager getDevicePolicyManager() {
+ synchronized (mLock) {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ }
+ return mDevicePolicyManager;
+ }
+ }
+
private PermissionManager getPermissionManager() {
synchronized (mLock) {
if (mPermissionManager == null) {
@@ -849,6 +863,18 @@
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public String getSupplementalProcessPackageName() {
+ try {
+ return mPM.getSupplementalProcessPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public boolean addPermission(PermissionInfo info) {
return getPermissionManager().addPermission(info, false);
@@ -1876,12 +1902,27 @@
if (!hasUserBadge(user.getIdentifier())) {
return icon;
}
+
+ final Drawable badgeForeground = getDevicePolicyManager().getDrawable(
+ getUpdatableUserIconBadgeId(user),
+ SOLID_COLORED,
+ () -> getDefaultUserIconBadge(user));
+
Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
- getUserManager().getUserIconBadgeResId(user.getIdentifier()),
+ badgeForeground,
getUserBadgeColor(user, false));
return getBadgedDrawable(icon, badge, null, true);
}
+ private String getUpdatableUserIconBadgeId(UserHandle user) {
+ return getUserManager().isManagedProfile(user.getIdentifier())
+ ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserIconBadge(UserHandle user) {
+ return mContext.getDrawable(getUserManager().getUserIconBadgeResId(user.getIdentifier()));
+ }
+
@Override
public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
Rect badgeLocation, int badgeDensity) {
@@ -1913,13 +1954,28 @@
if (badgeColor == null) {
return null;
}
- Drawable badgeForeground = getDrawableForDensity(
- getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+
+ final Drawable badgeForeground = getDevicePolicyManager().getDrawableForDensity(
+ getUpdatableUserBadgeId(user),
+ SOLID_COLORED,
+ density,
+ () -> getDefaultUserBadgeForDensity(user, density));
+
badgeForeground.setTint(getUserBadgeColor(user, false));
Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
return badge;
}
+ private String getUpdatableUserBadgeId(UserHandle user) {
+ return getUserManager().isManagedProfile(user.getIdentifier())
+ ? WORK_PROFILE_ICON : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserBadgeForDensity(UserHandle user, int density) {
+ return getDrawableForDensity(
+ getUserManager().getUserBadgeResId(user.getIdentifier()), density);
+ }
+
/**
* Returns the badge color based on whether device has dark theme enabled or not.
*/
@@ -1928,14 +1984,24 @@
if (!hasUserBadge(user.getIdentifier())) {
return null;
}
- Drawable badge = getDrawableForDensity(
- getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+
+ final Drawable badge = getDevicePolicyManager().getDrawableForDensity(
+ getUpdatableUserBadgeId(user),
+ SOLID_NOT_COLORED,
+ density,
+ () -> getDefaultUserBadgeNoBackgroundForDensity(user, density));
+
if (badge != null) {
badge.setTint(getUserBadgeColor(user, true));
}
return badge;
}
+ private Drawable getDefaultUserBadgeNoBackgroundForDensity(UserHandle user, int density) {
+ return getDrawableForDensity(
+ getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
+ }
+
private Drawable getDrawableForDensity(int drawableId, int density) {
if (density <= 0) {
density = mContext.getResources().getDisplayMetrics().densityDpi;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index db58c21..7845b6a 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -37,7 +37,8 @@
@Immutable
@DataClass(genEqualsHashCode = true,
genAidl = true,
- genHiddenConstructor = true)
+ genHiddenConstructor = true,
+ genToString = true)
// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
// getter
@DataClass.Suppress({"getOpCode"})
@@ -70,9 +71,13 @@
Preconditions.checkArgumentInRange(mOpCode, 0, AppOpsManager._NUM_OP - 1, "opCode");
}
+ private String opCodeToString() {
+ return getOp();
+ }
- // Code below generated by codegen v1.0.20.
+
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -160,6 +165,21 @@
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "AsyncNotedAppOp { " +
+ "opCode = " + opCodeToString() + ", " +
+ "notingUid = " + mNotingUid + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "message = " + mMessage + ", " +
+ "time = " + mTime +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
@@ -261,10 +281,10 @@
};
@DataClass.Generated(
- time = 1604456255752L,
- codegenVersion = "1.0.20",
+ time = 1643320606160L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
- inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nprivate java.lang.String opCodeToString()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true, genToString=true)")
@Deprecated
private void __metadata() {}
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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f3315a8..8181a74 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1997,7 +1997,8 @@
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
String instanceName, Handler handler, Executor executor, UserHandle user) {
- // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
+ // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
+ // ActivityManagerService.LocalService.startAndBindSupplementalProcessService
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
@@ -2023,10 +2024,10 @@
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess(this);
- int res = ActivityManager.getService().bindIsolatedService(
- mMainThread.getApplicationThread(), getActivityToken(), service,
- service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
+ int res = ActivityManager.getService().bindServiceInstance(
+ mMainThread.getApplicationThread(), getActivityToken(), service,
+ service.resolveTypeIfNeeded(getContentResolver()),
+ sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index a7fb83b..4b42ddc3 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -1449,15 +1449,9 @@
*
* Returns null if the dialog is not attached to a window with a decor.
*/
- @Nullable
+ @NonNull
@Override
public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
- if (mWindow != null) {
- View decorView = mWindow.getDecorView();
- if (decorView != null) {
- return decorView.getOnBackInvokedDispatcher();
- }
- }
- return null;
+ return ((OnBackInvokedDispatcherOwner) mWindow).getOnBackInvokedDispatcher();
}
}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 76471d3..040399e 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;
@@ -180,14 +181,18 @@
/**
* Returns if ANGLE is enabled for a given package and user ID.
* <p>
+ * ANGLE (Almost Native Graphics Layer Engine) can translate OpenGL ES commands to Vulkan
+ * commands. Enabling ANGLE may improve the performance and/or reduce the power consumption of
+ * applications.
* The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
*
* @hide
*/
+ @TestApi
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
- public @GameMode boolean getAngleEnabled(@NonNull String packageName) {
+ public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
try {
- return mService.getAngleEnabled(packageName, mContext.getUserId());
+ return mService.isAngleEnabled(packageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -204,4 +209,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/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 83c57c5..396e552 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -112,7 +112,7 @@
* calls, so this method should be the same as them to keep the invocation order.
*/
void overridePendingTransition(in IBinder token, in String packageName,
- int enterAnim, int exitAnim);
+ int enterAnim, int exitAnim, int backgroundColor);
int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
/** See {@link android.app.Activity#setDisablePreviewScreenshots}. */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a2578d6..7c48a57 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -169,7 +169,7 @@
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String callingPackage, int userId);
- int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+ int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String instanceName, in String callingPackage, int userId);
void updateServiceGroup(in IServiceConnection connection, int group, int importance);
@@ -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
@@ -679,6 +679,10 @@
*/
boolean isAppFreezerSupported();
+ /**
+ * Return whether the app freezer is enabled (true) or not (false) by this system.
+ */
+ boolean isAppFreezerEnabled();
/**
* Kills uid with the reason of permission change.
@@ -743,4 +747,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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 0801b24..c5add66 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -130,6 +130,9 @@
in ProfilerInfo profilerInfo, in Bundle options, int userId);
int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid,
int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId);
+ int startActivityFromGameSession(IApplicationThread caller, in String callingPackage,
+ in String callingFeatureId, int callingPid, int callingUid, in Intent intent,
+ int taskId, int userId);
void startRecentsActivity(in Intent intent, in long eventTime,
in IRecentsAnimationRunner recentsAnimationRunner);
int startActivityFromRecents(int taskId, in Bundle options);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1714229..77657d5 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -113,6 +113,7 @@
in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
+ void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void clearDnsCache();
void updateHttpProxy();
void setCoreSettings(in Bundle coreSettings);
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 57de8c7..7035ac0 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -26,7 +26,8 @@
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
int[] getAvailableGameModes(String packageName);
- boolean getAngleEnabled(String packageName, int userId);
+ boolean isAngleEnabled(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/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 14afd0f..5d1f4df 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -746,7 +746,7 @@
if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) {
throw new SecurityException("Requires SET_INITIAL_LOCK permission.");
}
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ return true;
}
private boolean hasPermission(String permission) {
@@ -814,6 +814,8 @@
/**
* Set the lockscreen password after validating against its expected complexity level.
*
+ * Below {@link android.os.Build.VERSION_CODES#S_V2}, this API will only work
+ * when {@link PackageManager.FEATURE_AUTOMOTIVE} is present.
* @param lockType - type of lock as specified in {@link LockTypes}
* @param password - password to validate; this has the same encoding
* as the output of String#getBytes
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4e32e9a..56c725e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -746,6 +746,10 @@
// default linker namespace.
continue;
}
+ if (info.isSdk()) {
+ // SDKs are not loaded automatically.
+ continue;
+ }
if (libsToLoadAfter.contains(info.getName())) {
if (DEBUG) {
Slog.v(ActivityThread.TAG,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d57c288..6a14772 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -17,6 +17,10 @@
package android.app;
import static android.annotation.Dimension.DP;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
@@ -39,6 +43,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -71,6 +76,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
@@ -5046,6 +5052,18 @@
}
// Note: This assumes that the current user can read the profile badge of the
// originating user.
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ return dpm.getDrawable(
+ getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION,
+ this::getDefaultProfileBadgeDrawable);
+ }
+
+ private String getUpdatableProfileBadgeId() {
+ return mContext.getSystemService(UserManager.class).isManagedProfile()
+ ? WORK_PROFILE_ICON : UNDEFINED;
+ }
+
+ private Drawable getDefaultProfileBadgeDrawable() {
return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
new UserHandle(mContext.getUserId()), 0);
}
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index ec8d989..715de14 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -16,7 +16,11 @@
package android.app;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -27,9 +31,10 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -104,17 +109,21 @@
* <pre>
* public class ActivityThread {
* ...
+ * private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
+ * new PropertyInvalidatedCache.QueryHandler<Integer, Birthday>() {
+ * {@literal @}Override
+ * public Birthday apply(Integer) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * };
* private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache
* private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
* private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new
- * PropertyInvalidatedCache<Integer, Birthday%>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
- * {@literal @}Override
- * protected Birthday recompute(Integer userId) {
- * return GetService("birthdayd").getUserBirthday(userId);
- * }
- * };
+ * PropertyInvalidatedCache<Integer, Birthday%>(
+ * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
+ *
* public void disableUserBirthdayCache() {
- * mBirthdayCache.disableLocal();
+ * mBirthdayCache.disableForCurrentProcess();
* }
* public void invalidateUserBirthdayCache() {
* mBirthdayCache.invalidateCache();
@@ -221,10 +230,124 @@
*
* @param <Query> The class used to index cache entries: must be hashable and comparable
* @param <Result> The class holding cache entries; use a boxed primitive if possible
- *
- * {@hide}
+ * @hide
*/
-public abstract class PropertyInvalidatedCache<Query, Result> {
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public class PropertyInvalidatedCache<Query, Result> {
+ /**
+ * This is a configuration class that customizes a cache instance.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static abstract class QueryHandler<Q,R> {
+ /**
+ * Compute a result given a query. The semantics are those of Functor.
+ */
+ public abstract @Nullable R apply(@NonNull Q query);
+
+ /**
+ * Return true if a query should not use the cache. The default implementation
+ * always uses the cache.
+ */
+ public boolean shouldBypassCache(@NonNull Q query) {
+ return false;
+ }
+ };
+
+ /**
+ * The system properties used by caches should be of the form <prefix>.<module>.<api>,
+ * where the prefix is "cache_key", the module is one of the constants below, and the
+ * api is any string. The ability to write the property (which happens during
+ * invalidation) depends on SELinux rules; these rules are defined against
+ * <prefix>.<module>. Therefore, the module chosen for a cache property must match
+ * the permissions granted to the processes that contain the corresponding caches.
+ * @hide
+ */
+ @IntDef(prefix = { "MODULE_" }, value = {
+ MODULE_TEST,
+ MODULE_SYSTEM,
+ MODULE_BLUETOOTH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Module {}
+
+ /**
+ * The module used for unit tests and cts tests. It is expected that no process in
+ * the system has permissions to write properties with this module.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_TEST = 0;
+
+ /**
+ * The module used for system server/framework caches. This is not visible outside
+ * the system processes.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_SYSTEM = 1;
+
+ /**
+ * The module used for bluetooth caches.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final int MODULE_BLUETOOTH = 2;
+
+ // A static array mapping module constants to strings.
+ private final static String[] sModuleNames =
+ { "test", "system_server", "bluetooth" };
+
+ /**
+ * Construct a system property that matches the rules described above. The module is
+ * one of the permitted values above. The API is a string that is a legal Java simple
+ * identifier. The api is modified to conform to the system property style guide by
+ * replacing every upper case letter with an underscore and the lower case equivalent.
+ * There is no requirement that the apiName be the name of an actual API.
+ *
+ * Be aware that SystemProperties has a maximum length which is private to the
+ * implementation. The current maximum is 92 characters. If this method creates a
+ * property name that is too long, SystemProperties.set() will fail without a good
+ * error message.
+ * @hide
+ */
+ @TestApi
+ public static @NonNull String createPropertyName(@Module int module, @NonNull String apiName) {
+ char[] api = apiName.toCharArray();
+ int upper = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isUpperCase(api[i])) {
+ upper++;
+ }
+ }
+ char[] suffix = new char[api.length + upper];
+ int j = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isJavaIdentifierPart(api[i])) {
+ if (Character.isUpperCase(api[i])) {
+ suffix[j++] = '_';
+ suffix[j++] = Character.toLowerCase(api[i]);
+ } else {
+ suffix[j++] = api[i];
+ }
+ } else {
+ throw new IllegalArgumentException("invalid api name");
+ }
+ }
+
+ String moduleName = null;
+ try {
+ moduleName = sModuleNames[module];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("invalid module " + module);
+ }
+
+ return "cache_key." + moduleName + "." + new String(suffix);
+ }
+
/**
* Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note
* that all values cause the cache to be skipped.
@@ -335,6 +458,25 @@
*/
private final String mCacheName;
+ /**
+ * The function that computes a Result, given a Query. This function is called on a
+ * cache miss.
+ */
+ private QueryHandler<Query, Result> mComputer;
+
+ /**
+ * A default function that delegates to the deprecated recompute() method.
+ */
+ private static class DefaultComputer<Query, Result> extends QueryHandler<Query, Result> {
+ final PropertyInvalidatedCache<Query, Result> mCache;
+ DefaultComputer(PropertyInvalidatedCache<Query, Result> cache) {
+ mCache = cache;
+ }
+ public Result apply(Query query) {
+ return mCache.recompute(query);
+ }
+ }
+
@GuardedBy("mLock")
private final LinkedHashMap<Query, Result> mCache;
@@ -359,8 +501,13 @@
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce.
+ *
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
this(maxEntries, propertyName, propertyName);
@@ -369,32 +516,73 @@
/**
* Make a new property invalidated cache.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce
* @param cacheName Name of this cache in debug and dumpsys
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
mPropertyName = propertyName;
mCacheName = cacheName;
mMaxEntries = maxEntries;
- mCache = new LinkedHashMap<Query, Result>(
+ mComputer = new DefaultComputer<>(this);
+ mCache = createMap();
+ registerCache();
+ }
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param maxEntries Maximum number of entries to cache; LRU discard
+ * @param module The module under which the cache key should be placed.
+ * @param api The api this cache front-ends. The api must be a Java identifier but
+ * need not be an actual api.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public PropertyInvalidatedCache(int maxEntries, @Module int module, @NonNull String api,
+ @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
+ mPropertyName = createPropertyName(module, api);
+ mCacheName = cacheName;
+ mMaxEntries = maxEntries;
+ mComputer = computer;
+ mCache = createMap();
+ registerCache();
+ }
+
+ // Create a map. This should be called only from the constructor.
+ private LinkedHashMap<Query, Result> createMap() {
+ return new LinkedHashMap<Query, Result>(
2 /* start small */,
0.75f /* default load factor */,
true /* LRU access order */) {
+ @GuardedBy("mLock")
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
final int size = size();
if (size > mHighWaterMark) {
mHighWaterMark = size;
}
- if (size > maxEntries) {
+ if (size > mMaxEntries) {
mMissOverflow++;
return true;
}
return false;
}
- };
+ };
+ }
+
+ // Register the map in the global list. If the cache is disabled globally, disable it
+ // now.
+ private void registerCache() {
synchronized (sCorkLock) {
sCaches.put(this, null);
if (sDisabledKeys.contains(mCacheName)) {
@@ -418,8 +606,9 @@
/**
* Enable or disable testing. The testing property map is cleared every time this
* method is called.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void setTestMode(boolean mode) {
sTesting = mode;
synchronized (sTestingPropertyMap) {
@@ -431,13 +620,22 @@
* Enable testing the specific cache key. Only keys in the map are subject to testing.
* There is no method to stop testing a property name. Just disable the test mode.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static void testPropertyName(@NonNull String name) {
+ private static void testPropertyName(@NonNull String name) {
synchronized (sTestingPropertyMap) {
sTestingPropertyMap.put(name, (long) NONCE_UNSET);
}
}
+ /**
+ * Enable testing the specific cache key. Only keys in the map are subject to testing.
+ * There is no method to stop testing a property name. Just disable the test mode.
+ * @hide
+ */
+ @TestApi
+ public void testPropertyName() {
+ testPropertyName(mPropertyName);
+ }
+
// Read the system property associated with the current cache. This method uses the
// handle for faster reading.
private long getCurrentNonce() {
@@ -490,6 +688,9 @@
/**
* Forget all cached values.
+ * TODO(216112648) remove this as a public API. Clients should invalidate caches, not clear
+ * them.
+ * @hide
*/
public final void clear() {
synchronized (mLock) {
@@ -505,22 +706,29 @@
* Fetch a result from scratch in case it's not in the cache at all. Called unlocked: may
* block. If this function returns null, the result of the cache query is null. There is no
* "negative cache" in the query: we don't cache null results at all.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
- public abstract @NonNull Result recompute(@NonNull Query query);
+ public Result recompute(@NonNull Query query) {
+ return mComputer.apply(query);
+ }
/**
* Return true if the query should bypass the cache. The default behavior is to
* always use the cache but the method can be overridden for a specific class.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
public boolean bypass(@NonNull Query query) {
- return false;
+ return mComputer.shouldBypassCache(query);
}
/**
- * Determines if a pair of responses are considered equal. Used to determine whether a
- * cache is inadvertently returning stale results when VERIFY is set to true. Some
- * existing clients override this method, but it is now deprecated in favor of a valid
- * equals() method on the Result class.
+ * Determines if a pair of responses are considered equal. Used to determine whether
+ * a cache is inadvertently returning stale results when VERIFY is set to true.
+ * @hide
*/
public boolean resultEquals(Result cachedResult, Result fetchedResult) {
// If a service crashes and returns a null result, the cached value remains valid.
@@ -541,6 +749,7 @@
* the meantime (if the nonce has changed in the meantime, we drop the cache and try the
* whole query again), or 3) null, which causes the old value to be removed from the cache
* and null to be returned as the result of the cache query.
+ * @hide
*/
protected Result refresh(Result oldResult, Query query) {
return oldResult;
@@ -551,7 +760,7 @@
* testing. To disable a cache in normal code, use disableLocal().
* @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableInstance() {
synchronized (mLock) {
mDisabled = true;
@@ -580,9 +789,10 @@
* disabled remain disabled (the "disabled" setting is sticky). However, new caches
* with this name will not be disabled. It is not an error if the cache name is not
* found in the list of disabled caches.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final void clearDisableLocal() {
+ @TestApi
+ public final void forgetDisableLocal() {
synchronized (sCorkLock) {
sDisabledKeys.remove(mCacheName);
}
@@ -592,25 +802,43 @@
* Disable this cache in the current process, and all other caches that use the same
* name. This does not affect caches that have a different name but use the same
* property.
+ * TODO(216112648) Remove this in favor of disableForCurrentProcess().
+ * @hide
*/
public final void disableLocal() {
+ disableForCurrentProcess();
+ }
+
+ /**
+ * Disable this cache in the current process, and all other caches that use the same
+ * name. This does not affect caches that have a different name but use the same
+ * property.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final void disableForCurrentProcess() {
disableLocal(mCacheName);
}
/**
- * Return whether the cache is disabled in this process.
+ * Return whether a cache instance is disabled.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final boolean isDisabledLocal() {
+ @TestApi
+ public final boolean isDisabled() {
return mDisabled || !sEnabled;
}
/**
* Get a value from the cache or recompute it.
+ * @hide
*/
- public @NonNull Result query(@NonNull Query query) {
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
- long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
+ long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
if (bypass(query)) {
currentNonce = NONCE_BYPASS;
}
@@ -724,8 +952,9 @@
* When multiple caches share a single property value, using an instance method on one of
* the cache objects to invalidate all of the cache objects becomes confusing and you should
* just use the static version of this function.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableSystemWide() {
disableSystemWide(mPropertyName);
}
@@ -746,16 +975,33 @@
/**
* Non-static convenience version of invalidateCache() for situations in which only a single
* PropertyInvalidatedCache is keyed on a particular property value.
+ * @hide
*/
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public final void invalidateCache() {
invalidateCache(mPropertyName);
}
/**
+ * Invalidate caches in all processes that are keyed for the module and api.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static void invalidateCache(@Module int module, @NonNull String api) {
+ invalidateCache(createPropertyName(module, api));
+ }
+
+ /**
* Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
* {@var name}. This function is synchronous: caches are invalidated upon return.
*
+ * TODO(216112648) make this method private in favor of the two-argument (module, api)
+ * override.
+ *
* @param name Name of the cache-key property to invalidate
+ * @hide
*/
public static void invalidateCache(@NonNull String name) {
if (!sEnabled) {
@@ -824,6 +1070,7 @@
* corkInvalidations() and uncorkInvalidations() must be called in pairs.
*
* @param name Name of the cache-key property to cork
+ * @hide
*/
public static void corkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -871,6 +1118,7 @@
* transitioning it to normal operation (unless explicitly disabled system-wide).
*
* @param name Name of the cache-key property to uncork
+ * @hide
*/
public static void uncorkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -916,6 +1164,7 @@
* The auto-cork delay is configurable but it should not be too long. The purpose of
* the delay is to minimize the number of times a server writes to the system property
* when invalidating the cache. One write every 50ms does not hurt system performance.
+ * @hide
*/
public static final class AutoCorker {
public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
@@ -1043,6 +1292,8 @@
* Return the query as a string, to be used in debug messages. New clients should not
* override this, but should instead add the necessary toString() method to the Query
* class.
+ * TODO(216112648) add a method in the QueryHandler and deprecate this API.
+ * @hide
*/
protected @NonNull String queryToString(@NonNull Query query) {
return Objects.toString(query);
@@ -1054,8 +1305,9 @@
* process does not have privileges to write SystemProperties. Once disabled it is not
* possible to re-enable caching in the current process. If a client wants to
* temporarily disable caching, use the corking mechanism.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void disableForTestMode() {
Log.d(TAG, "disabling all caches in the process");
sEnabled = false;
@@ -1064,10 +1316,11 @@
/**
* Report the disabled status of this cache instance. The return value does not
* reflect status of the property key.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public boolean getDisabledState() {
- return isDisabledLocal();
+ return isDisabled();
}
/**
@@ -1133,7 +1386,8 @@
}
/**
- * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor.
+ * Dumps contents of every cache in the process to the provided ParcelFileDescriptor.
+ * @hide
*/
public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
ArrayList<PropertyInvalidatedCache> activeCaches;
@@ -1174,6 +1428,7 @@
/**
* Trim memory by clearing all the caches.
+ * @hide
*/
public static void onTrimMemory() {
for (PropertyInvalidatedCache pic : getActiveCaches()) {
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..8fcb07f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +28,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Icon;
+import android.media.MediaRoute2Info;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -39,11 +41,13 @@
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.NotificationVisibility;
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 +218,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;
@@ -309,6 +341,166 @@
@Retention(RetentionPolicy.SOURCE)
public @interface NavBarModeOverride {}
+ /**
+ * State indicating that this sender device is close to a receiver device, so the user can
+ * potentially *start* a cast to the receiver device if the user moves their device a bit
+ * closer.
+ * <p>
+ * Important notes:
+ * <ul>
+ * <li>This state represents that the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.</li>
+ * <li>This state is for *starting* a cast. It should be used when this device is currently
+ * playing media locally and the media should be transferred to be played on the receiver
+ * device instead.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST = 0;
+
+ /**
+ * State indicating that this sender device is close to a receiver device, so the user can
+ * potentially *end* a cast on the receiver device if the user moves this device a bit closer.
+ * <p>
+ * Important notes:
+ * <ul>
+ * <li>This state represents that the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.</li>
+ * <li>This state is for *ending* a cast. It should be used when media is currently being
+ * played on the receiver device and the media should be transferred to play locally
+ * instead.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST = 1;
+
+ /**
+ * State indicating that a media transfer from this sender device to a receiver device has been
+ * started.
+ * <p>
+ * Important note: This state is for *starting* a cast. It should be used when this device is
+ * currently playing media locally and the media has started being transferred to the receiver
+ * device instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED = 2;
+
+ /**
+ * State indicating that a media transfer from the receiver and back to this sender device
+ * has been started.
+ * <p>
+ * Important note: This state is for *ending* a cast. It should be used when media is currently
+ * being played on the receiver device and the media has started being transferred to play
+ * locally instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED = 3;
+
+ /**
+ * State indicating that a media transfer from this sender device to a receiver device has
+ * finished successfully.
+ * <p>
+ * Important note: This state is for *starting* a cast. It should be used when this device had
+ * previously been playing media locally and the media has successfully been transferred to the
+ * receiver device instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 4;
+
+ /**
+ * State indicating that a media transfer from the receiver and back to this sender device has
+ * finished successfully.
+ * <p>
+ * Important note: This state is for *ending* a cast. It should be used when media was
+ * previously being played on the receiver device and has been successfully transferred to play
+ * locally on this device instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED = 5;
+
+ /**
+ * State indicating that the attempted transfer to the receiver device has failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED = 6;
+
+ /**
+ * State indicating that the attempted transfer back to this device has failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED = 7;
+
+ /**
+ * State indicating that this sender device is no longer close to the receiver device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER = 8;
+
+ /** @hide */
+ @IntDef(prefix = {"MEDIA_TRANSFER_SENDER_STATE_"}, value = {
+ MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaTransferSenderState {}
+
+ /**
+ * State indicating that this receiver device is close to a sender device, so the user can
+ * potentially start or end a cast to the receiver device if the user moves the sender device a
+ * bit closer.
+ * <p>
+ * Important note: This state represents that the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0;
+
+ /**
+ * State indicating that this receiver device is no longer close to the sender device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"MEDIA_TRANSFER_RECEIVER_STATE_"}, value = {
+ MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaTransferReceiverState {}
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -760,6 +952,81 @@
return navBarModeOverride;
}
+ /**
+ * Notifies the system of a new media tap-to-transfer state for the <b>sender</b> device.
+ *
+ * <p>The callback should only be provided for the {@link
+ * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED} or {@link
+ * MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED} states, since those are the
+ * only states where an action can be un-done.
+ *
+ * @param displayState the new state for media tap-to-transfer.
+ * @param routeInfo the media route information for the media being transferred.
+ * @param undoExecutor an executor to run the callback on and must be provided if the
+ * callback is non-null.
+ * @param undoCallback a callback that will be triggered if the user elects to undo a media
+ * transfer.
+ *
+ * @throws IllegalArgumentException if an undo callback is provided for states that are not a
+ * succeeded state.
+ * @throws IllegalArgumentException if an executor is not provided when a callback is.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void updateMediaTapToTransferSenderDisplay(
+ @MediaTransferSenderState int displayState,
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable Executor undoExecutor,
+ @Nullable Runnable undoCallback
+ ) {
+ Objects.requireNonNull(routeInfo);
+ if (displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED
+ && displayState != MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED
+ && undoCallback != null) {
+ throw new IllegalArgumentException(
+ "The undoCallback should only be provided when the state is a "
+ + "transfer succeeded state");
+ }
+ if (undoCallback != null && undoExecutor == null) {
+ throw new IllegalArgumentException(
+ "You must pass an executor when you pass an undo callback");
+ }
+ IStatusBarService svc = getService();
+ try {
+ UndoCallback callbackProxy = null;
+ if (undoExecutor != null) {
+ callbackProxy = new UndoCallback(undoExecutor, undoCallback);
+ }
+ svc.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, callbackProxy);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the system of a new media tap-to-transfer state for the <b>receiver</b> device.
+ *
+ * @param displayState the new state for media tap-to-transfer.
+ * @param routeInfo the media route information for the media being transferred.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void updateMediaTapToTransferReceiverDisplay(
+ @MediaTransferReceiverState int displayState,
+ @NonNull MediaRoute2Info routeInfo) {
+ Objects.requireNonNull(routeInfo);
+ IStatusBarService svc = getService();
+ try {
+ svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String windowStateToString(int state) {
if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
@@ -1042,4 +1309,29 @@
mExecutor.execute(() -> mCallback.accept(userResponse));
}
}
+
+ /**
+ * @hide
+ */
+ static final class UndoCallback extends IUndoMediaTransferCallback.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final Runnable mCallback;
+
+ UndoCallback(@NonNull Executor executor, @NonNull Runnable callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onUndoTriggered() {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(mCallback);
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index 7c0c08a..f156b30 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -40,7 +40,8 @@
@DataClass(
genEqualsHashCode = true,
genAidl = true,
- genConstructor = false
+ genConstructor = false,
+ genToString = true
)
@DataClass.Suppress({"getOpCode", "getOpMode"})
public final class SyncNotedAppOp implements Parcelable {
@@ -118,6 +119,10 @@
return mOpMode;
}
+ private String opCodeToString() {
+ return getOp();
+ }
+
// Code below generated by codegen v1.0.23.
@@ -153,6 +158,20 @@
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "SyncNotedAppOp { " +
+ "opMode = " + mOpMode + ", " +
+ "opCode = " + opCodeToString() + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "packageName = " + mPackageName +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SyncNotedAppOp other) { ... }
@@ -245,10 +264,10 @@
};
@DataClass.Generated(
- time = 1619711733947L,
+ time = 1643320427700L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
- inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
+ inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7f8e46e..4187ba0 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;
@@ -49,6 +51,8 @@
import android.app.usage.NetworkStatsManager;
import android.app.usage.StorageStatsManager;
import android.app.usage.UsageStatsManager;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
import android.apphibernation.AppHibernationManager;
import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothFrameworkInitializer;
@@ -135,12 +139,10 @@
import android.net.ConnectivityFrameworkInitializerTiramisu;
import android.net.EthernetManager;
import android.net.IEthernetManager;
-import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.IPacProxyManager;
import android.net.IVpnManager;
-import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
@@ -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;
@@ -435,15 +439,6 @@
return new VcnManager(ctx, service);
}});
- registerService(Context.IPSEC_SERVICE, IpSecManager.class,
- new CachedServiceFetcher<IpSecManager>() {
- @Override
- public IpSecManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getService(Context.IPSEC_SERVICE);
- IIpSecService service = IIpSecService.Stub.asInterface(b);
- return new IpSecManager(ctx, service);
- }});
-
registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
new StaticServiceFetcher<CountryDetector>() {
@Override
@@ -494,6 +489,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
@@ -1019,15 +1025,14 @@
}});
registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
- new CachedServiceFetcher<PersistentDataBlockManager>() {
+ new StaticServiceFetcher<PersistentDataBlockManager>() {
@Override
- public PersistentDataBlockManager createService(ContextImpl ctx)
- throws ServiceNotFoundException {
+ public PersistentDataBlockManager createService() throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE);
IPersistentDataBlockService persistentDataBlockService =
IPersistentDataBlockService.Stub.asInterface(b);
if (persistentDataBlockService != null) {
- return new PersistentDataBlockManager(ctx, persistentDataBlockService);
+ return new PersistentDataBlockManager(persistentDataBlockService);
} else {
// not supported
return null;
@@ -1248,6 +1253,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
@@ -1272,6 +1288,20 @@
}
});
+ registerService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE,
+ WallpaperEffectsGenerationManager.class,
+ new CachedServiceFetcher<WallpaperEffectsGenerationManager>() {
+ @Override
+ public WallpaperEffectsGenerationManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(
+ Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ return b == null ? null :
+ new WallpaperEffectsGenerationManager(
+ IWallpaperEffectsGenerationManager.Stub.asInterface(b));
+ }
+ });
+
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
@Override
public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 18f9379..3d2c03d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -214,6 +214,12 @@
public boolean topActivityInSizeCompat;
/**
+ * Whether the direct top activity is eligible for letterbox education.
+ * @hide
+ */
+ public boolean topActivityEligibleForLetterboxEducation;
+
+ /**
* Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
* supports), this is what the system actually uses for resizability based on other policy and
* developer options.
@@ -398,7 +404,8 @@
/** @hide */
public boolean hasCompatUI() {
- return hasCameraCompatControl() || topActivityInSizeCompat;
+ return hasCameraCompatControl() || topActivityInSizeCompat
+ || topActivityEligibleForLetterboxEducation;
}
/**
@@ -460,6 +467,8 @@
return displayId == that.displayId
&& taskId == that.taskId
&& topActivityInSizeCompat == that.topActivityInSizeCompat
+ && topActivityEligibleForLetterboxEducation
+ == that.topActivityEligibleForLetterboxEducation
&& cameraCompatControlState == that.cameraCompatControlState
// Bounds are important if top activity has compat controls.
&& (!hasCompatUI() || configuration.windowConfiguration.getBounds()
@@ -507,6 +516,7 @@
isVisible = source.readBoolean();
isSleeping = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
+ topActivityEligibleForLetterboxEducation = source.readBoolean();
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
cameraCompatControlState = source.readInt();
@@ -551,6 +561,7 @@
dest.writeBoolean(isVisible);
dest.writeBoolean(isSleeping);
dest.writeBoolean(topActivityInSizeCompat);
+ dest.writeBoolean(topActivityEligibleForLetterboxEducation);
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeInt(cameraCompatControlState);
@@ -585,6 +596,8 @@
+ " isVisible=" + isVisible
+ " isSleeping=" + isSleeping
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
+ + " topActivityEligibleForLetterboxEducation= "
+ + topActivityEligibleForLetterboxEducation
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " cameraCompatControlState="
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 00903a8..b41b5f0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -780,6 +780,33 @@
}
/**
+ * Sets the system settings values that control the scaling factor for animations. The scale
+ * controls the animation playback speed for animations that respect these settings. Animations
+ * that do not respect the settings values will not be affected by this function. A lower scale
+ * value results in a faster speed. A value of <code>0</code> disables animations entirely. When
+ * animations are disabled services receive window change events more quickly which can reduce
+ * the potential by confusion by reducing the time during which windows are in transition.
+ *
+ * @see AccessibilityEvent#TYPE_WINDOWS_CHANGED
+ * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.provider.Settings.Global#WINDOW_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#TRANSITION_ANIMATION_SCALE
+ * @see android.provider.Settings.Global#ANIMATOR_DURATION_SCALE
+ * @param scale The scaling factor for all animations.
+ */
+ public void setAnimationScale(float scale) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setAnimationScale(scale);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
* A request for WindowManagerService to wait until all animations have completed and input
* information has been sent from WindowManager to native InputManager.
*
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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3960f4e..b0883b6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -59,6 +59,7 @@
import android.net.PrivateDnsConnectivityChecker;
import android.net.ProxyInfo;
import android.net.Uri;
+import android.net.wifi.WifiSsid;
import android.nfc.NfcAdapter;
import android.os.Binder;
import android.os.Build;
@@ -111,6 +112,7 @@
import java.lang.annotation.RetentionPolicy;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
@@ -454,6 +456,70 @@
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* </ul>
*
+ * <p>Once the device admin app is set as the device owner, the following APIs are available for
+ * managing polices on the device:
+ * <ul>
+ * <li>{@link #isDeviceManaged()}</li>
+ * <li>{@link #isUninstallBlocked(ComponentName, String)}</li>
+ * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li>
+ * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li>
+ * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li>
+ * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li>
+ * <li>{@link #getOrganizationName(ComponentName)} </li>
+ * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li>
+ * <li>{@link #getShortSupportMessage(ComponentName)}</li>
+ * <li>{@link #isBackupServiceEnabled(ComponentName)}</li>
+ * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li>
+ * <li>{@link #isLockTaskPermitted(String)}</li>
+ * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features
+ * can be set (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+ * </ul>
+ * <li>{@link #getLockTaskFeatures(ComponentName)}</li>
+ * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
+ * <li>{@link #getLockTaskPackages(ComponentName)}</li>
+ * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li>
+ * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li>
+ * <li>{@link #wipeData(int)}</li>
+ * <li>{@link #isDeviceOwnerApp(String)}</li>
+ * <li>{@link #clearDeviceOwnerApp(String)}</li>
+ * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where
+ * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+ * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or
+ * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin
+ * app (otherwise a {@link SecurityException} will be thrown)</li>
+ * <li>{@link #getPermissionGrantState(ComponentName, String, String)}, where
+ * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be
+ * used and device admin app is the only package that can be used to retrieve the permission
+ * permission grant state for (otherwise a {@link SecurityException} will be thrown)</li>
+ * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions
+ * are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+ * <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+ * <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+ * <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+ * <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+ * <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+ * </ul>
+ * <li>{@link #getUserRestrictions(ComponentName)}</li>
+ * <li>{@link #clearUserRestriction(ComponentName, String)}, where the following user
+ * restrictions are permitted (otherwise a {@link SecurityException} will be thrown):</li>
+ * <ul>
+ * <li>{@link UserManager#DISALLOW_ADD_USER}</li>
+ * <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li>
+ * <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li>
+ * <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li>
+ * <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li>
+ * <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li>
+ * </ul>
+ * </ul>
+ *
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -587,7 +653,7 @@
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update
* to the role holder is required.
*
- * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
+ * <p>This result code can be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}.
*
* @hide
*/
@@ -595,15 +661,19 @@
public static final int RESULT_UPDATE_ROLE_HOLDER = 2;
/**
- * A {@link Bundle} extra which describes the state of the role holder at the time when it
- * returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
+ * A {@link PersistableBundle} extra which the role holder can use to describe its own state
+ * when it returns {@link #RESULT_UPDATE_ROLE_HOLDER}.
*
- * <p>After the update completes, the role holder's {@link
- * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
+ * <p>If {@link #RESULT_UPDATE_ROLE_HOLDER} was accompanied by this extra, after the update
+ * completes, the role holder's {@link #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link
* #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched,
* which will contain this extra. It is the role holder's responsibility to restore its
* state from this extra.
*
+ * <p>The content of this {@link PersistableBundle} is entirely up to the role holder. It
+ * should contain anything the role holder needs to restore its original state when it gets
+ * restarted.
+ *
* @hide
*/
@SystemApi
@@ -1280,7 +1350,10 @@
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
* {@link #ACTION_PROVISION_MANAGED_DEVICE}
+ *
+ * @deprecated Logo customization is no longer supported in the provisioning flow.
*/
+ @Deprecated
public static final String EXTRA_PROVISIONING_LOGO_URI =
"android.app.extra.PROVISIONING_LOGO_URI";
@@ -3204,6 +3277,7 @@
*
* @hide
*/
+ @TestApi
public static final int DEVICE_OWNER_TYPE_DEFAULT = 0;
/**
@@ -3211,6 +3285,7 @@
*
* @hide
*/
+ @TestApi
public static final int DEVICE_OWNER_TYPE_FINANCED = 1;
/**
@@ -3506,7 +3581,8 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void acknowledgeNewUserDisclaimer() {
if (mService != null) {
@@ -3519,6 +3595,25 @@
}
/**
+ * Checks whether the new managed user disclaimer was viewed by the current user.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ @TestApi
+ public boolean isNewUserDisclaimerAcknowledged() {
+ if (mService != null) {
+ try {
+ return mService.isNewUserDisclaimerAcknowledged();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Return true if the given administrator component is currently active (enabled) in the system.
*
* @param admin The administrator component to check for.
@@ -10063,7 +10158,9 @@
/**
* Called by a profile owner of secondary user that is affiliated with the device to stop the
- * calling user and switch back to primary user.
+ * calling user and switch back to primary user (when the user was
+ * {@link #switchUser(ComponentName, UserHandle)} switched to) or stop the user (when it was
+ * {@link #startUserInBackground(ComponentName, UserHandle) started in background}.
*
* <p>Notice that on devices running with
* {@link UserManager#isHeadlessSystemUserMode() headless system user mode}, there is no primary
@@ -10091,12 +10188,17 @@
}
/**
- * Same as {@link #logoutUser(ComponentName)}, but called by system (like Settings), not admin.
+ * Similar to {@link #logoutUser(ComponentName)}, except:
+ *
+ * <ul>
+ * <li>Called by system (like Settings), not admin.
+ * <li>It logs out the current user, not the caller.
+ * </ul>
*
* @hide
*/
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.CREATE_USERS})
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public @UserOperationResult int logoutUser() {
// TODO(b/214336184): add CTS test
@@ -10108,7 +10210,10 @@
}
/**
* Gets the user a {@link #logoutUser(ComponentName)} call would switch to,
- * or {@code null} if the current user is not in a session.
+ * or {@code null} if the current user is not in a session (i.e., if it was not
+ * {@link #switchUser(ComponentName, UserHandle) switched} or
+ * {@link #startUserInBackground(ComponentName, UserHandle) started in background} by the
+ * device admin.
*
* @hide
*/
@@ -14577,6 +14682,7 @@
*
* @hide
*/
+ @TestApi
public void setDeviceOwnerType(@NonNull ComponentName admin,
@DeviceOwnerType int deviceOwnerType) {
throwIfParentInstance("setDeviceOwnerType");
@@ -14600,6 +14706,7 @@
*
* @hide
*/
+ @TestApi
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
throwIfParentInstance("getDeviceOwnerType");
@@ -14767,6 +14874,11 @@
* The device may not connect to networks that do not meet the minimum security level.
* If the current network does not meet the minimum security level set, it will be disconnected.
*
+ * The following shows the Wi-Fi security levels from the lowest to the highest security level:
+ * {@link #WIFI_SECURITY_OPEN}
+ * {@link #WIFI_SECURITY_PERSONAL}
+ * {@link #WIFI_SECURITY_ENTERPRISE_EAP}
+ * {@link #WIFI_SECURITY_ENTERPRISE_192}
*
* @param level minimum security level
* @throws SecurityException if the caller is not a device owner or a profile owner on
@@ -14819,10 +14931,14 @@
mService.setSsidAllowlist(new ArrayList<>());
} else {
int policyType = policy.getPolicyType();
+ List<String> ssidList = new ArrayList<>();
+ for (WifiSsid ssid : policy.getSsids()) {
+ ssidList.add(new String(ssid.getBytes(), StandardCharsets.UTF_8));
+ }
if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
- mService.setSsidAllowlist(new ArrayList<>(policy.getSsids()));
+ mService.setSsidAllowlist(ssidList);
} else {
- mService.setSsidDenylist(new ArrayList<>(policy.getSsids()));
+ mService.setSsidDenylist(ssidList);
}
}
} catch (RemoteException e) {
@@ -14848,11 +14964,21 @@
try {
List<String> allowlist = mService.getSsidAllowlist();
if (!allowlist.isEmpty()) {
- return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(allowlist));
+ List<WifiSsid> wifiSsidAllowlist = new ArrayList<>();
+ for (String ssid : allowlist) {
+ wifiSsidAllowlist.add(
+ WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8)));
+ }
+ return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(wifiSsidAllowlist));
}
List<String> denylist = mService.getSsidDenylist();
if (!denylist.isEmpty()) {
- return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(denylist));
+ List<WifiSsid> wifiSsidDenylist = new ArrayList<>();
+ for (String ssid : denylist) {
+ wifiSsidDenylist.add(
+ WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8)));
+ }
+ return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(wifiSsidDenylist));
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -14943,8 +15069,8 @@
* <p>Also returns the drawable from {@code defaultDrawableLoader} if
* {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
* set a different value use
@@ -14961,7 +15087,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -14977,8 +15103,8 @@
* {@link #getDrawable(String, String, Callable)}
* if an override was set for that specific source.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -14989,7 +15115,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15032,8 +15158,8 @@
* Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15046,7 +15172,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawableForDensity(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15064,8 +15190,8 @@
* Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
* {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
*
- * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultDrawableLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15079,7 +15205,7 @@
* @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
* the provided params.
*/
- @NonNull
+ @Nullable
public Drawable getDrawableForDensity(
@NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
@NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
@@ -15118,7 +15244,7 @@
/**
* For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string
* resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID
- * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any
+ * {@code callingPackageResourceId} (see {@link DevicePolicyResources.Strings}), meaning any
* system UI surface calling {@link #getString} with {@code stringId} will get
* the new resource after this API is called.
*
@@ -15154,7 +15280,7 @@
/**
* Removes the updated strings for the list of {@code stringIds} (see
- * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings},
+ * {@link DevicePolicyResources.Strings}) that was previously set by calling {@link #setStrings},
* meaning any subsequent calls to {@link #getString} for the provided IDs will
* return the default string from {@code defaultStringLoader}.
*
@@ -15179,14 +15305,14 @@
/**
* Returns the appropriate updated string for the {@code stringId} (see
- * {@link DevicePolicyResources.String}) if one was set using
+ * {@link DevicePolicyResources.Strings}) if one was set using
* {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}.
*
* <p>Also returns the string from {@code defaultStringLoader} if
- * {@link DevicePolicyResources.String#INVALID_ID} was passed.
+ * {@link DevicePolicyResources.Strings#UNDEFINED} was passed.
*
- * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultStringLoader} returned {@code null}.
*
* <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
* notified when a resource has been updated.
@@ -15201,7 +15327,7 @@
* @hide
*/
@SystemApi
- @NonNull
+ @Nullable
public String getString(
@NonNull @DevicePolicyResources.UpdatableStringId String stringId,
@NonNull Callable<String> defaultStringLoader) {
@@ -15236,8 +15362,8 @@
* {@link java.util.Formatter} and {@link java.lang.String#format}, (see
* {@link Resources#getString(int, Object...)}).
*
- * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
- * {@link NullPointerException} is thrown.
+ * <p>Calls to this API will not return {@code null} unless no updated drawable was found
+ * and the call to {@code defaultStringLoader} returned {@code null}.
*
* @param stringId The IDs to get the updated resource for.
* @param defaultStringLoader To get the default string if no updated string was set for
@@ -15247,7 +15373,7 @@
* @hide
*/
@SystemApi
- @NonNull
+ @Nullable
@SuppressLint("SamShouldBeLast")
public String getString(
@NonNull @DevicePolicyResources.UpdatableStringId String stringId,
@@ -15277,4 +15403,45 @@
}
return ParcelableResource.loadDefaultString(defaultStringLoader);
}
+
+ /**
+ * Returns a boolean for whether the DPC has been downloaded during provisioning.
+ *
+ * <p>If true is returned, then any attempts to begin setup again should result in factory reset
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public boolean isDpcDownloaded() {
+ throwIfParentInstance("isDpcDownloaded");
+ if (mService != null) {
+ try {
+ return mService.isDpcDownloaded();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Use to indicate that the DPC has or has not been downloaded during provisioning.
+ *
+ * @param downloaded {@code true} if the dpc has been downloaded during provisioning. false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void setDpcDownloaded(boolean downloaded) {
+ throwIfParentInstance("setDpcDownloaded");
+ if (mService != null) {
+ try {
+ mService.setDpcDownloaded(downloaded);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 2ad2010..7f2e5fd 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -58,6 +58,10 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_INCOMING_WORK_CALL_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_MISSED_WORK_CALL_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_ONGOING_WORK_CALL_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_WIFI_WORK_CALL_LABEL;
import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE;
import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE;
@@ -93,6 +97,173 @@
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.LOCATION_AUTO_GRANTED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_WORK_ACCOUNT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCOUNTS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_THIS_DEVICE_ADMIN_APP;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVE_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT_MINIMUM;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_CAMERA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_LOCATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_MICROPHONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_COUNT_ESTIMATED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_INSTALLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_CURRENT_INPUT_METHOD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_DEFAULT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_HTTP_PROXY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_INPUT_METHOD_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_LOCK_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_APPS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_BUG_REPORT_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_NETWORK_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_SECURITY_LOGS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_USAGE_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_WORK_DATA_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_PERMISSIONS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_DEVICE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_PERSONAL_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTACT_YOUR_IT_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITHOUT_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_IT_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENTERPRISE_PRIVACY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ERROR_MOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_FOR_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.HOW_TO_DISCONNECT_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.IT_ADMIN_POLICY_DISABLING_INFO_URL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NO_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS_NONE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONNECT_TRUSTED_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPTIONS_DISABLED_BY_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PASSWORD_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.PIN_RECENTLY_USED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_AND_UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SELECT_DEVICE_ADMIN_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_POSTSETUP_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PASSWORD_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PIN_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.UNINSTALL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.USER_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ADMIN_POLICIES_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ALARM_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_APP_SUBTEXT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCATION_SWITCH_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCKED_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_MANAGED_BY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_OFF_CONDITION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_RINGTONE_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SECURITY_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.YOUR_ACCESS_TO_THIS_DEVICE_TITLE;
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;
@@ -159,7 +330,8 @@
Drawables.WORK_PROFILE_OFF_ICON,
Drawables.WORK_PROFILE_USER_ICON
})
- public @interface UpdatableDrawableId {}
+ public @interface UpdatableDrawableId {
+ }
/**
* Identifiers to specify the desired style for the updatable device management system
@@ -174,7 +346,8 @@
Drawables.Style.OUTLINE,
Drawables.Style.DEFAULT
})
- public @interface UpdatableDrawableStyle {}
+ public @interface UpdatableDrawableStyle {
+ }
/**
* Identifiers to specify the location if the updatable device management system resource.
@@ -191,7 +364,8 @@
Drawables.Source.QUICK_SETTINGS,
Drawables.Source.STATUS_BAR
})
- public @interface UpdatableDrawableSource {}
+ public @interface UpdatableDrawableSource {
+ }
/**
* Resource identifiers used to update device management-related string resources.
@@ -231,7 +405,7 @@
PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE,
PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE,
- NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
+ NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE,
NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN,
SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK,
FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB,
@@ -257,7 +431,96 @@
SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE,
BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE,
BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE,
- WORK_PROFILE_PAUSED_MESSAGE
+ WORK_PROFILE_PAUSED_MESSAGE,
+
+ // Settings Strings
+ FACE_SETTINGS_FOR_WORK_TITLE, WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE,
+ WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK, WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE,
+ WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE,
+ WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE,
+ WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE,
+ WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE, WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
+ ACCESSIBILITY_CATEGORY_WORK, ACCESSIBILITY_CATEGORY_PERSONAL,
+ ACCESSIBILITY_WORK_ACCOUNT_TITLE, ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE,
+ WORK_PROFILE_LOCATION_SWITCH_TITLE, SET_WORK_PROFILE_PASSWORD_HEADER,
+ SET_WORK_PROFILE_PIN_HEADER, SET_WORK_PROFILE_PATTERN_HEADER,
+ CONFIRM_WORK_PROFILE_PASSWORD_HEADER, CONFIRM_WORK_PROFILE_PIN_HEADER,
+ CONFIRM_WORK_PROFILE_PATTERN_HEADER, REENTER_WORK_PROFILE_PASSWORD_HEADER,
+ REENTER_WORK_PROFILE_PIN_HEADER, WORK_PROFILE_CONFIRM_PATTERN, WORK_PROFILE_CONFIRM_PIN,
+ WORK_PROFILE_PASSWORD_REQUIRED, WORK_PROFILE_SECURITY_TITLE,
+ WORK_PROFILE_UNIFY_LOCKS_TITLE, WORK_PROFILE_UNIFY_LOCKS_SUMMARY,
+ WORK_PROFILE_UNIFY_LOCKS_DETAIL, WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT,
+ WORK_PROFILE_KEYBOARDS_AND_TOOLS, WORK_PROFILE_NOT_AVAILABLE, WORK_PROFILE_SETTING,
+ WORK_PROFILE_SETTING_ON_SUMMARY, WORK_PROFILE_SETTING_OFF_SUMMARY, REMOVE_WORK_PROFILE,
+ DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING,
+ WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, WORK_PROFILE_CONFIRM_REMOVE_TITLE,
+ WORK_PROFILE_CONFIRM_REMOVE_MESSAGE, WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
+ WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER, WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE,
+ WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY, WORK_PROFILE_RINGTONE_TITLE,
+ WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE, WORK_PROFILE_ALARM_RINGTONE_TITLE,
+ WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY,
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE,
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE,
+ WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER, WORK_PROFILE_LOCKED_NOTIFICATION_TITLE,
+ WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE,
+ WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY,
+ WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, CONNECTED_WORK_AND_PERSONAL_APPS_TITLE,
+ CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA, ONLY_CONNECT_TRUSTED_APPS,
+ HOW_TO_DISCONNECT_APPS, CONNECT_APPS_DIALOG_TITLE, CONNECT_APPS_DIALOG_SUMMARY,
+ APP_CAN_ACCESS_PERSONAL_DATA, APP_CAN_ACCESS_PERSONAL_PERMISSIONS,
+ INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT,
+ INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT, WORK_PROFILE_MANAGED_BY, MANAGED_BY,
+ WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING, DISABLED_BY_IT_ADMIN_TITLE,
+ CONTACT_YOUR_IT_ADMIN, WORK_PROFILE_ADMIN_POLICIES_WARNING, USER_ADMIN_POLICIES_WARNING,
+ DEVICE_ADMIN_POLICIES_WARNING, WORK_PROFILE_OFF_CONDITION_TITLE,
+ MANAGED_PROFILE_SETTINGS_TITLE, WORK_PROFILE_CONTACT_SEARCH_TITLE,
+ WORK_PROFILE_CONTACT_SEARCH_SUMMARY, CROSS_PROFILE_CALENDAR_TITLE,
+ CROSS_PROFILE_CALENDAR_SUMMARY, ALWAYS_ON_VPN_PERSONAL_PROFILE, ALWAYS_ON_VPN_DEVICE,
+ ALWAYS_ON_VPN_WORK_PROFILE, CA_CERTS_PERSONAL_PROFILE, CA_CERTS_WORK_PROFILE,
+ CA_CERTS_DEVICE, ADMIN_CAN_LOCK_DEVICE, ADMIN_CAN_WIPE_DEVICE,
+ ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE,
+ ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE, DEVICE_MANAGED_WITHOUT_NAME,
+ DEVICE_MANAGED_WITH_NAME, WORK_PROFILE_APP_SUBTEXT, PERSONAL_PROFILE_APP_SUBTEXT,
+ FINGERPRINT_FOR_WORK, FACE_UNLOCK_DISABLED, FINGERPRINT_UNLOCK_DISABLED,
+ FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, PIN_RECENTLY_USED, PASSWORD_RECENTLY_USED,
+ MANAGE_DEVICE_ADMIN_APPS, NUMBER_OF_DEVICE_ADMINS_NONE, NUMBER_OF_DEVICE_ADMINS,
+ FORGOT_PASSWORD_TITLE, FORGOT_PASSWORD_TEXT, ERROR_MOVE_DEVICE_ADMIN,
+ DEVICE_ADMIN_SETTINGS_TITLE, REMOVE_DEVICE_ADMIN, UNINSTALL_DEVICE_ADMIN,
+ REMOVE_AND_UNINSTALL_DEVICE_ADMIN, SELECT_DEVICE_ADMIN_APPS, NO_DEVICE_ADMINS,
+ ACTIVATE_DEVICE_ADMIN_APP, ACTIVATE_THIS_DEVICE_ADMIN_APP,
+ ACTIVATE_DEVICE_ADMIN_APP_TITLE, NEW_DEVICE_ADMIN_WARNING,
+ NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED, ACTIVE_DEVICE_ADMIN_WARNING,
+ SET_PROFILE_OWNER_TITLE, SET_PROFILE_OWNER_DIALOG_TITLE,
+ SET_PROFILE_OWNER_POSTSETUP_WARNING, OTHER_OPTIONS_DISABLED_BY_ADMIN,
+ REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION, IT_ADMIN_POLICY_DISABLING_INFO_URL,
+ SHARE_REMOTE_BUGREPORT_DIALOG_TITLE, SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT,
+ SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT, SHARING_REMOTE_BUGREPORT_MESSAGE,
+ MANAGED_DEVICE_INFO, MANAGED_DEVICE_INFO_SUMMARY, MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME,
+ ENTERPRISE_PRIVACY_HEADER, INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE,
+ CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE, YOUR_ACCESS_TO_THIS_DEVICE_TITLE,
+ ADMIN_CAN_SEE_WORK_DATA_WARNING, ADMIN_CAN_SEE_APPS_WARNING,
+ ADMIN_CAN_SEE_USAGE_WARNING, ADMIN_CAN_SEE_NETWORK_LOGS_WARNING,
+ ADMIN_CAN_SEE_BUG_REPORT_WARNING, ADMIN_CAN_SEE_SECURITY_LOGS_WARNING,
+ ADMIN_ACTION_NONE, ADMIN_ACTION_APPS_INSTALLED, ADMIN_ACTION_APPS_COUNT_ESTIMATED,
+ ADMIN_ACTIONS_APPS_COUNT_MINIMUM, ADMIN_ACTION_ACCESS_LOCATION,
+ ADMIN_ACTION_ACCESS_MICROPHONE, ADMIN_ACTION_ACCESS_CAMERA,
+ ADMIN_ACTION_SET_DEFAULT_APPS, ADMIN_ACTIONS_APPS_COUNT,
+ ADMIN_ACTION_SET_CURRENT_INPUT_METHOD, ADMIN_ACTION_SET_INPUT_METHOD_NAME,
+ ADMIN_ACTION_SET_HTTP_PROXY, WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY,
+ WORK_PROFILE_PRIVACY_POLICY_INFO, CONNECTED_APPS_SEARCH_KEYWORDS,
+ WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS, ACCOUNTS_SEARCH_KEYWORDS,
+ CONTROLLED_BY_ADMIN_SUMMARY, WORK_PROFILE_USER_LABEL, WORK_CATEGORY_HEADER,
+ PERSONAL_CATEGORY_HEADER,
+
+ // PermissionController Strings
+ WORK_PROFILE_DEFAULT_APPS_TITLE, HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE,
+ BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+ BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE,
+ LOCATION_AUTO_GRANTED_MESSAGE,
+
+ // Dialer Strings
+ NOTIFICATION_INCOMING_WORK_CALL_TITLE, NOTIFICATION_ONGOING_WORK_CALL_TITLE,
+ NOTIFICATION_MISSED_WORK_CALL_TITLE, NOTIFICATION_WIFI_WORK_CALL_LABEL,
})
public @interface UpdatableStringId {
}
@@ -432,7 +695,8 @@
@SystemApi
public static final class Strings {
- private Strings() {}
+ private Strings() {
+ }
/**
* An ID for any string that can't be updated.
@@ -446,23 +710,1222 @@
private static Set<String> buildStringsSet() {
Set<String> strings = new HashSet<>();
+ strings.addAll(Settings.buildStringsSet());
strings.addAll(Launcher.buildStringsSet());
strings.addAll(SystemUi.buildStringsSet());
strings.addAll(Core.buildStringsSet());
strings.addAll(DocumentsUi.buildStringsSet());
strings.addAll(MediaProvider.buildStringsSet());
+ strings.addAll(PermissionController.buildStringsSet());
+ strings.addAll(Dialer.buildStringsSet());
return strings;
}
/**
* Class containing the identifiers used to update device management-related system strings
+ * in the Settings package
+ *
+ * @hide
+ */
+ public static final class Settings {
+
+ private Settings() {
+ }
+
+ private static final String PREFIX = "Settings.";
+
+ /**
+ * Title shown for menu item that launches face settings or enrollment, for work profile
+ */
+ public static final String FACE_SETTINGS_FOR_WORK_TITLE =
+ PREFIX + "FACE_SETTINGS_FOR_WORK_TITLE";
+
+ /**
+ * Warning when removing the last fingerprint on a work profile
+ */
+ public static final String WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE =
+ PREFIX + "WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE";
+
+ /**
+ * Text letting the user know that their IT admin can't reset their screen lock if they
+ * forget it, and they can choose to set another lock that would be specifically for
+ * their work apps
+ */
+ public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK =
+ PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK";
+
+ /**
+ * Message shown in screen lock picker for setting up a work profile screen lock
+ */
+ public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE =
+ PREFIX + "WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE";
+
+ /**
+ * Title for PreferenceScreen to launch picker for security method for the managed
+ * profile when there is none
+ */
+ public static final String WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE =
+ PREFIX + "WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user only has one attempt left to provide the
+ * work lock pattern before the work profile is removed
+ */
+ public static final String WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE =
+ PREFIX + "WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE";
+
+ /**
+ * Content of the dialog shown when the user has failed to provide the device lock too
+ * many times and the device is wiped
+ */
+ public static final String WORK_PROFILE_LOCK_ATTEMPTS_FAILED =
+ PREFIX + "WORK_PROFILE_LOCK_ATTEMPTS_FAILED";
+
+ /**
+ * Content description for work profile accounts group
+ */
+ public static final String ACCESSIBILITY_CATEGORY_WORK =
+ PREFIX + "ACCESSIBILITY_CATEGORY_WORK";
+
+ /**
+ * Content description for personal profile accounts group
+ */
+ public static final String ACCESSIBILITY_CATEGORY_PERSONAL =
+ PREFIX + "ACCESSIBILITY_CATEGORY_PERSONAL";
+
+ /**
+ * Content description for work profile details page title
+ */
+ public static final String ACCESSIBILITY_WORK_ACCOUNT_TITLE =
+ PREFIX + "ACCESSIBILITY_WORK_ACCOUNT_TITLE";
+
+ /**
+ * Content description for personal profile details page title
+ */
+ public static final String ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE =
+ PREFIX + "ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE";
+
+ /**
+ * Title for work profile location switch
+ */
+ public static final String WORK_PROFILE_LOCATION_SWITCH_TITLE =
+ PREFIX + "WORK_PROFILE_LOCATION_SWITCH_TITLE";
+
+ /**
+ * Header when setting work profile password
+ */
+ public static final String SET_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when setting work profile PIN
+ */
+ public static final String SET_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Header when setting work profile pattern
+ */
+ public static final String SET_WORK_PROFILE_PATTERN_HEADER =
+ PREFIX + "SET_WORK_PROFILE_PATTERN_HEADER";
+
+ /**
+ * Header when confirming work profile password
+ */
+ public static final String CONFIRM_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when confirming work profile pin
+ */
+ public static final String CONFIRM_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Header when confirming work profile pattern
+ */
+ public static final String CONFIRM_WORK_PROFILE_PATTERN_HEADER =
+ PREFIX + "CONFIRM_WORK_PROFILE_PATTERN_HEADER";
+
+ /**
+ * Header when re-entering work profile password
+ */
+ public static final String REENTER_WORK_PROFILE_PASSWORD_HEADER =
+ PREFIX + "REENTER_WORK_PROFILE_PASSWORD_HEADER";
+
+ /**
+ * Header when re-entering work profile pin
+ */
+ public static final String REENTER_WORK_PROFILE_PIN_HEADER =
+ PREFIX + "REENTER_WORK_PROFILE_PIN_HEADER";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work pattern to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PATTERN =
+ PREFIX + "WORK_PROFILE_CONFIRM_PATTERN";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work pin to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PIN =
+ PREFIX + "WORK_PROFILE_CONFIRM_PIN";
+
+ /**
+ * Message to be used to explain the users that they need to enter their work password
+ * to
+ * continue a particular operation
+ */
+ public static final String WORK_PROFILE_CONFIRM_PASSWORD =
+ PREFIX + "WORK_PROFILE_CONFIRM_PASSWORD";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pattern
+ * that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PATTERN_REQUIRED =
+ PREFIX + "WORK_PROFILE_PATTERN_REQUIRED";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pin
+ * that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PIN_REQUIRED =
+ PREFIX + "WORK_PROFILE_PIN_REQUIRED";
+
+ /**
+ * This string shows = PREFIX + "shows"; up on a screen where a user can enter a
+ * password that lets them access
+ * their work profile. This is an extra security measure that's required for them to
+ * continue
+ */
+ public static final String WORK_PROFILE_PASSWORD_REQUIRED =
+ PREFIX + "WORK_PROFILE_PASSWORD_REQUIRED";
+
+ /**
+ * Header for Work Profile security settings
+ */
+ public static final String WORK_PROFILE_SECURITY_TITLE =
+ PREFIX + "WORK_PROFILE_SECURITY_TITLE";
+
+ /**
+ * Header for Work Profile unify locks settings
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_TITLE =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_TITLE";
+
+ /**
+ * Setting option explanation to unify work and personal locks
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_SUMMARY =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_SUMMARY";
+
+ /**
+ * Further explanation when the user wants to unify work and personal locks
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_DETAIL =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_DETAIL";
+
+ /**
+ * Ask if the user wants to create a new lock for personal and work as the current work
+ * lock is not enough for the device
+ */
+ public static final String WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT =
+ PREFIX + "WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT";
+
+ /**
+ * Title of 'Work profile keyboards & tools' preference category
+ */
+ public static final String WORK_PROFILE_KEYBOARDS_AND_TOOLS =
+ PREFIX + "WORK_PROFILE_KEYBOARDS_AND_TOOLS";
+
+ /**
+ * Label for state when work profile is not available
+ */
+ public static final String WORK_PROFILE_NOT_AVAILABLE =
+ PREFIX + "WORK_PROFILE_NOT_AVAILABLE";
+
+ /**
+ * Label for work profile setting (to allow turning work profile on and off)
+ */
+ public static final String WORK_PROFILE_SETTING = PREFIX + "WORK_PROFILE_SETTING";
+
+ /**
+ * Description of the work profile setting when the work profile is on
+ */
+ public static final String WORK_PROFILE_SETTING_ON_SUMMARY =
+ PREFIX + "WORK_PROFILE_SETTING_ON_SUMMARY";
+
+ /**
+ * Description of the work profile setting when the work profile is off
+ */
+ public static final String WORK_PROFILE_SETTING_OFF_SUMMARY =
+ PREFIX + "WORK_PROFILE_SETTING_OFF_SUMMARY";
+
+ /**
+ * Button text to remove work profile
+ */
+ public static final String REMOVE_WORK_PROFILE = PREFIX + "REMOVE_WORK_PROFILE";
+
+ /**
+ * Text of message to show to device owner user whose administrator has installed a SSL
+ * CA Cert
+ */
+ public static final String DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+ PREFIX + "DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+ /**
+ * Text of message to show to work profile users whose administrator has installed a SSL
+ * CA Cert
+ */
+ public static final String WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING =
+ PREFIX + "WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING";
+
+ /**
+ * Work profile removal confirmation title
+ */
+ public static final String WORK_PROFILE_CONFIRM_REMOVE_TITLE =
+ PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_TITLE";
+
+ /**
+ * Work profile removal confirmation message
+ */
+ public static final String WORK_PROFILE_CONFIRM_REMOVE_MESSAGE =
+ PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_MESSAGE";
+
+ /**
+ * Toast shown when an app in the work profile attempts to open notification settings
+ * and apps in the work profile cannot access notification settings
+ */
+ public static final String WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS =
+ PREFIX + "WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS";
+
+ /**
+ * Work sound settings section header
+ */
+ public static final String WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER =
+ PREFIX + "WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER";
+
+ /**
+ * Title for the switch that enables syncing of personal ringtones to work profile
+ */
+ public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE =
+ PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE";
+
+ /**
+ * Summary for the switch that enables syncing of personal ringtones to work profile
+ */
+ public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY =
+ PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY";
+
+ /**
+ * Title for the option defining the work profile phone ringtone
+ */
+ public static final String WORK_PROFILE_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_RINGTONE_TITLE";
+
+ /**
+ * Title for the option defining the default work profile notification ringtone
+ */
+ public static final String WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE";
+
+ /**
+ * Title for the option defining the default work alarm ringtone
+ */
+ public static final String WORK_PROFILE_ALARM_RINGTONE_TITLE =
+ PREFIX + "WORK_PROFILE_ALARM_RINGTONE_TITLE";
+
+ /**
+ * Summary for sounds when sync with personal sounds is active
+ */
+ public static final String WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY =
+ PREFIX + "WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY";
+
+ /**
+ * Title for dialog shown when enabling sync with personal sounds
+ */
+ public static final String
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE =
+ PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE";
+
+ /**
+ * Message for dialog shown when using the same sounds for work events as for personal
+ * events
+ */
+ public static final String
+ ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE =
+ PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE";
+
+ /**
+ * Work profile notifications section header
+ */
+ public static final String WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER =
+ PREFIX + "WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER";
+
+ /**
+ * Title for the option controlling notifications for work profile
+ */
+ public static final String WORK_PROFILE_LOCKED_NOTIFICATION_TITLE =
+ PREFIX + "WORK_PROFILE_LOCKED_NOTIFICATION_TITLE";
+
+ /**
+ * Title for redacting sensitive content on lockscreen for work profiles
+ */
+ public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE =
+ PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE";
+
+ /**
+ * Summary for redacting sensitive content on lockscreen for work profiles
+ */
+ public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY =
+ PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY";
+
+ /**
+ * Indicates that the work profile admin doesn't allow this notification listener to
+ * access
+ * work profile notifications
+ */
+ public static final String WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED =
+ PREFIX + "WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED";
+
+ /**
+ * This setting shows a user's connected work and personal apps.
+ */
+ public static final String CONNECTED_WORK_AND_PERSONAL_APPS_TITLE =
+ PREFIX + "CONNECTED_WORK_AND_PERSONAL_APPS_TITLE";
+
+ /**
+ * This text lets a user know that if they connect work and personal apps,
+ * they will share permissions and can access each other's data
+ */
+ public static final String CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA =
+ PREFIX + "CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA";
+
+ /**
+ * This text lets a user know that they should only connect work and personal apps if
+ * they
+ * trust the work app with their personal data
+ */
+ public static final String ONLY_CONNECT_TRUSTED_APPS =
+ PREFIX + "ONLY_CONNECT_TRUSTED_APPS";
+
+ /**
+ * This text lets a user know how to disconnect work and personal apps
+ */
+ public static final String HOW_TO_DISCONNECT_APPS = PREFIX + "HOW_TO_DISCONNECT_APPS";
+
+ /**
+ * Title of confirmation dialog when connecting work and personal apps
+ */
+ public static final String CONNECT_APPS_DIALOG_TITLE =
+ PREFIX + "CONNECT_APPS_DIALOG_TITLE";
+
+ /**
+ * This dialog is shown when a user tries to connect a work app to a personal
+ * app
+ */
+ public static final String CONNECT_APPS_DIALOG_SUMMARY =
+ PREFIX + "CONNECT_APPS_DIALOG_SUMMARY";
+
+ /**
+ * This text lets the user know that their work app will be able to access data in their
+ * personal app
+ */
+ public static final String APP_CAN_ACCESS_PERSONAL_DATA =
+ PREFIX + "APP_CAN_ACCESS_PERSONAL_DATA";
+
+ /**
+ * This text lets the user know that their work app will be able to use permissions in
+ * their personal app
+ */
+ public static final String APP_CAN_ACCESS_PERSONAL_PERMISSIONS =
+ PREFIX + "APP_CAN_ACCESS_PERSONAL_PERMISSIONS";
+
+ /**
+ * lets a user know that they need to install an app in their work profile in order to
+ * connect it to the corresponding personal app
+ */
+ public static final String INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT =
+ PREFIX + "INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT";
+
+ /**
+ * lets a user know that they need to install an app in their personal profile in order
+ * to
+ * connect it to the corresponding work app
+ */
+ public static final String INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT =
+ PREFIX + "INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT";
+
+ /**
+ * Header for showing the organisation managing the work profile
+ */
+ public static final String WORK_PROFILE_MANAGED_BY = PREFIX + "WORK_PROFILE_MANAGED_BY";
+
+ /**
+ * Summary showing the enterprise who manages the device or profile.
+ */
+ public static final String MANAGED_BY = PREFIX + "MANAGED_BY";
+
+ /**
+ * Warning message about disabling usage access on profile owner
+ */
+ public static final String WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING =
+ PREFIX + "WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING";
+
+ /**
+ * Title for dialog displayed when user taps a setting on their phone that's blocked by
+ * their IT admin
+ */
+ public static final String DISABLED_BY_IT_ADMIN_TITLE =
+ PREFIX + "DISABLED_BY_IT_ADMIN_TITLE";
+
+ /**
+ * Shown when the user tries to change phone settings that are blocked by their IT admin
+ */
+ public static final String CONTACT_YOUR_IT_ADMIN = PREFIX + "CONTACT_YOUR_IT_ADMIN";
+
+ /**
+ * warn user about policies the admin can set in a work profile
+ */
+ public static final String WORK_PROFILE_ADMIN_POLICIES_WARNING =
+ PREFIX + "WORK_PROFILE_ADMIN_POLICIES_WARNING";
+
+ /**
+ * warn user about policies the admin can set on a user
+ */
+ public static final String USER_ADMIN_POLICIES_WARNING =
+ PREFIX + "USER_ADMIN_POLICIES_WARNING";
+
+ /**
+ * warn user about policies the admin can set on a device
+ */
+ public static final String DEVICE_ADMIN_POLICIES_WARNING =
+ PREFIX + "DEVICE_ADMIN_POLICIES_WARNING";
+
+ /**
+ * Condition that work profile is off
+ */
+ public static final String WORK_PROFILE_OFF_CONDITION_TITLE =
+ PREFIX + "WORK_PROFILE_OFF_CONDITION_TITLE";
+
+ /**
+ * Title of work profile setting page
+ */
+ public static final String MANAGED_PROFILE_SETTINGS_TITLE =
+ PREFIX + "MANAGED_PROFILE_SETTINGS_TITLE";
+
+ /**
+ * Setting that lets a user's personal apps identify contacts using the user's work
+ * directory
+ */
+ public static final String WORK_PROFILE_CONTACT_SEARCH_TITLE =
+ PREFIX + "WORK_PROFILE_CONTACT_SEARCH_TITLE";
+
+ /**
+ * This setting lets a user's personal apps identify contacts using the user's work
+ * directory
+ */
+ public static final String WORK_PROFILE_CONTACT_SEARCH_SUMMARY =
+ PREFIX + "WORK_PROFILE_CONTACT_SEARCH_SUMMARY";
+
+ /**
+ * This setting lets the user show their work events on their personal calendar
+ */
+ public static final String CROSS_PROFILE_CALENDAR_TITLE =
+ PREFIX + "CROSS_PROFILE_CALENDAR_TITLE";
+
+ /**
+ * Setting description. If the user turns on this setting, they can see their work
+ * events on their personal calendar
+ */
+ public static final String CROSS_PROFILE_CALENDAR_SUMMARY =
+ PREFIX + "CROSS_PROFILE_CALENDAR_SUMMARY";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin in the personal profile
+ */
+ public static final String ALWAYS_ON_VPN_PERSONAL_PROFILE =
+ PREFIX + "ALWAYS_ON_VPN_PERSONAL_PROFILE";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin for the entire device
+ */
+ public static final String ALWAYS_ON_VPN_DEVICE = PREFIX + "ALWAYS_ON_VPN_DEVICE";
+
+ /**
+ * Label explaining that an always-on VPN was set by the admin in the work profile
+ */
+ public static final String ALWAYS_ON_VPN_WORK_PROFILE =
+ PREFIX + "ALWAYS_ON_VPN_WORK_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates in personal profile
+ */
+ public static final String CA_CERTS_PERSONAL_PROFILE =
+ PREFIX + "CA_CERTS_PERSONAL_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates in work profile
+ */
+ public static final String CA_CERTS_WORK_PROFILE = PREFIX + "CA_CERTS_WORK_PROFILE";
+
+ /**
+ * Label explaining that the admin installed trusted CA certificates for the entire
+ * device
+ */
+ public static final String CA_CERTS_DEVICE = PREFIX + "CA_CERTS_DEVICE";
+
+ /**
+ * Label explaining that the admin can lock the device and change the user's password
+ */
+ public static final String ADMIN_CAN_LOCK_DEVICE = PREFIX + "ADMIN_CAN_LOCK_DEVICE";
+
+ /**
+ * Label explaining that the admin can wipe the device remotely
+ */
+ public static final String ADMIN_CAN_WIPE_DEVICE = PREFIX + "ADMIN_CAN_WIPE_DEVICE";
+
+ /**
+ * Label explaining that the admin configured the device to wipe itself when the
+ * password is mistyped too many times
+ */
+ public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE =
+ PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE";
+
+ /**
+ * Label explaining that the admin configured the work profile to wipe itself when the
+ * password is mistyped too many times
+ */
+ public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE =
+ PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE";
+
+ /**
+ * Message indicating that the device is enterprise-managed by a Device Owner
+ */
+ public static final String DEVICE_MANAGED_WITHOUT_NAME =
+ PREFIX + "DEVICE_MANAGED_WITHOUT_NAME";
+
+ /**
+ * Message indicating that the device is enterprise-managed by a Device Owner
+ */
+ public static final String DEVICE_MANAGED_WITH_NAME =
+ PREFIX + "DEVICE_MANAGED_WITH_NAME";
+
+ /**
+ * Subtext of work profile app for current setting
+ */
+ public static final String WORK_PROFILE_APP_SUBTEXT =
+ PREFIX + "WORK_PROFILE_APP_SUBTEXT";
+
+ /**
+ * Subtext of personal profile app for current setting
+ */
+ public static final String PERSONAL_PROFILE_APP_SUBTEXT =
+ PREFIX + "PERSONAL_PROFILE_APP_SUBTEXT";
+
+ /**
+ * Title shown for work menu item that launches fingerprint settings or enrollment
+ */
+ public static final String FINGERPRINT_FOR_WORK = PREFIX + "FINGERPRINT_FOR_WORK";
+
+ /**
+ * Message shown in face enrollment dialog, when face unlock is disabled by device admin
+ */
+ public static final String FACE_UNLOCK_DISABLED = PREFIX + "FACE_UNLOCK_DISABLED";
+
+ /**
+ * message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled
+ * by device admin
+ */
+ public static final String FINGERPRINT_UNLOCK_DISABLED =
+ PREFIX + "FINGERPRINT_UNLOCK_DISABLED";
+
+ /**
+ * Text shown in fingerprint settings explaining what the fingerprint can be used for in
+ * the case unlocking is disabled
+ */
+ public static final String FINGERPRINT_UNLOCK_DISABLED_EXPLANATION =
+ PREFIX + "FINGERPRINT_UNLOCK_DISABLED_EXPLANATION";
+
+ /**
+ * Error shown when in PIN mode and PIN has been used recently
+ */
+ public static final String PIN_RECENTLY_USED = PREFIX + "PIN_RECENTLY_USED";
+
+ /**
+ * Error shown when in PASSWORD mode and password has been used recently
+ */
+ public static final String PASSWORD_RECENTLY_USED = PREFIX + "PASSWORD_RECENTLY_USED";
+
+ /**
+ * Title of preference to manage device admin apps
+ */
+ public static final String MANAGE_DEVICE_ADMIN_APPS =
+ PREFIX + "MANAGE_DEVICE_ADMIN_APPS";
+
+ /**
+ * Inform the user that currently no device admin apps are installed and active
+ */
+ public static final String NUMBER_OF_DEVICE_ADMINS_NONE =
+ PREFIX + "NUMBER_OF_DEVICE_ADMINS_NONE";
+
+ /**
+ * Inform the user how many device admin apps are installed and active
+ */
+ public static final String NUMBER_OF_DEVICE_ADMINS = PREFIX + "NUMBER_OF_DEVICE_ADMINS";
+
+ /**
+ * Title that asks the user to contact the IT admin to reset password
+ */
+ public static final String FORGOT_PASSWORD_TITLE = PREFIX + "FORGOT_PASSWORD_TITLE";
+
+ /**
+ * Content that asks the user to contact the IT admin to reset password
+ */
+ public static final String FORGOT_PASSWORD_TEXT = PREFIX + "FORGOT_PASSWORD_TEXT";
+
+ /**
+ * Error message shown when trying to move device administrators to external disks, such
+ * as SD card
+ */
+ public static final String ERROR_MOVE_DEVICE_ADMIN = PREFIX + "ERROR_MOVE_DEVICE_ADMIN";
+
+ /**
+ * Device admin app settings title
+ */
+ public static final String DEVICE_ADMIN_SETTINGS_TITLE =
+ PREFIX + "DEVICE_ADMIN_SETTINGS_TITLE";
+
+ /**
+ * Button to remove the active device admin app
+ */
+ public static final String REMOVE_DEVICE_ADMIN = PREFIX + "REMOVE_DEVICE_ADMIN";
+
+ /**
+ * Button to uninstall the device admin app
+ */
+ public static final String UNINSTALL_DEVICE_ADMIN = PREFIX + "UNINSTALL_DEVICE_ADMIN";
+
+ /**
+ * Button to deactivate and uninstall the device admin app
+ */
+ public static final String REMOVE_AND_UNINSTALL_DEVICE_ADMIN =
+ PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
+
+ /**
+ * Title for selecting device admin apps
+ */
+ public static final String SELECT_DEVICE_ADMIN_APPS =
+ PREFIX + "SELECT_DEVICE_ADMIN_APPS";
+
+ /**
+ * Message when there are no available device admin apps to display
+ */
+ public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
+
+ /**
+ * Title for screen to add a device admin app
+ */
+ public static final String ACTIVATE_DEVICE_ADMIN_APP =
+ PREFIX + "ACTIVATE_DEVICE_ADMIN_APP";
+
+ /**
+ * Label for button to set the active device admin
+ */
+ public static final String ACTIVATE_THIS_DEVICE_ADMIN_APP =
+ PREFIX + "ACTIVATE_THIS_DEVICE_ADMIN_APP";
+
+ /**
+ * Activate a specific device admin app title
+ */
+ public static final String ACTIVATE_DEVICE_ADMIN_APP_TITLE =
+ PREFIX + "ACTIVATE_DEVICE_ADMIN_APP_TITLE";
+
+ /**
+ * Device admin warning message about policies a not active admin can use
+ */
+ public static final String NEW_DEVICE_ADMIN_WARNING =
+ PREFIX + "NEW_DEVICE_ADMIN_WARNING";
+
+ /**
+ * Simplified device admin warning message
+ */
+ public static final String NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED =
+ PREFIX + "NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED";
+
+ /**
+ * Device admin warning message about policies the active admin can use
+ */
+ public static final String ACTIVE_DEVICE_ADMIN_WARNING =
+ PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
+
+ /**
+ * Title for screen to set a profile owner
+ */
+ public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
+
+ /**
+ * Simplified title for dialog to set a profile owner
+ */
+ public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
+ PREFIX + "SET_PROFILE_OWNER_DIALOG_TITLE";
+
+ /**
+ * Warning when trying to add a profile owner admin after setup has completed
+ */
+ public static final String SET_PROFILE_OWNER_POSTSETUP_WARNING =
+ PREFIX + "SET_PROFILE_OWNER_POSTSETUP_WARNING";
+
+ /**
+ * Message displayed to let the user know that some of the options are disabled by admin
+ */
+ public static final String OTHER_OPTIONS_DISABLED_BY_ADMIN =
+ PREFIX + "OTHER_OPTIONS_DISABLED_BY_ADMIN";
+
+ /**
+ * This is shown if the authenticator for a given account fails to remove it due to
+ * admin restrictions
+ */
+ public static final String REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION =
+ PREFIX + "REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION";
+
+ /**
+ * Url for learning more about IT admin policy disabling
+ */
+ public static final String IT_ADMIN_POLICY_DISABLING_INFO_URL =
+ PREFIX + "IT_ADMIN_POLICY_DISABLING_INFO_URL";
+
+ /**
+ * Title of dialog shown to ask for user consent for sharing a bugreport that was
+ * requested
+ * remotely by the IT administrator
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_DIALOG_TITLE =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_DIALOG_TITLE";
+
+ /**
+ * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+ * requested remotely by the IT administrator
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT";
+
+ /**
+ * Message of a dialog shown to ask for user consent for sharing a bugreport that was
+ * requested remotely by the IT administrator and it's still being taken
+ */
+ public static final String SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT =
+ PREFIX + "SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT";
+
+ /**
+ * Message of a dialog shown to inform that the remote bugreport that was requested
+ * remotely by the IT administrator is still being taken and will be shared when
+ * finished
+ */
+ public static final String SHARING_REMOTE_BUGREPORT_MESSAGE =
+ PREFIX + "SHARING_REMOTE_BUGREPORT_MESSAGE";
+
+ /**
+ * Managed device information screen title
+ */
+ public static final String MANAGED_DEVICE_INFO = PREFIX + "MANAGED_DEVICE_INFO";
+
+ /**
+ * Summary for managed device info section
+ */
+ public static final String MANAGED_DEVICE_INFO_SUMMARY =
+ PREFIX + "MANAGED_DEVICE_INFO_SUMMARY";
+
+ /**
+ * Summary for managed device info section including organization name
+ */
+ public static final String MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME =
+ PREFIX + "MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME";
+
+ /**
+ * Enterprise Privacy settings header, summarizing the powers that the admin has
+ */
+ public static final String ENTERPRISE_PRIVACY_HEADER =
+ PREFIX + "ENTERPRISE_PRIVACY_HEADER";
+
+ /**
+ * Types of information your organization can see section title
+ */
+ public static final String INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE =
+ PREFIX + "INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE";
+
+ /**
+ * Changes made by your organization's admin section title
+ */
+ public static final String CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE =
+ PREFIX + "CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE";
+
+ /**
+ * Your access to this device section title
+ */
+ public static final String YOUR_ACCESS_TO_THIS_DEVICE_TITLE =
+ PREFIX + "YOUR_ACCESS_TO_THIS_DEVICE_TITLE";
+
+ /**
+ * Things the admin can see: data associated with the work account
+ */
+ public static final String ADMIN_CAN_SEE_WORK_DATA_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_WORK_DATA_WARNING";
+
+ /**
+ * Things the admin can see: Apps installed on the device
+ */
+ public static final String ADMIN_CAN_SEE_APPS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_APPS_WARNING";
+
+ /**
+ * Things the admin can see: Amount of time and data spent in each app
+ */
+ public static final String ADMIN_CAN_SEE_USAGE_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_USAGE_WARNING";
+
+ /**
+ * Things the admin can see: Most recent network traffic log
+ */
+ public static final String ADMIN_CAN_SEE_NETWORK_LOGS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_NETWORK_LOGS_WARNING";
+ /**
+ * Things the admin can see: Most recent bug report
+ */
+ public static final String ADMIN_CAN_SEE_BUG_REPORT_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_BUG_REPORT_WARNING";
+
+ /**
+ * Things the admin can see: Security logs
+ */
+ public static final String ADMIN_CAN_SEE_SECURITY_LOGS_WARNING =
+ PREFIX + "ADMIN_CAN_SEE_SECURITY_LOGS_WARNING";
+
+ /**
+ * Indicate that the admin never took a given action so far (e.g. did not retrieve
+ * security logs or request bug reports).
+ */
+ public static final String ADMIN_ACTION_NONE = PREFIX + "ADMIN_ACTION_NONE";
+
+ /**
+ * Indicate that the admin installed one or more apps on the device
+ */
+ public static final String ADMIN_ACTION_APPS_INSTALLED =
+ PREFIX + "ADMIN_ACTION_APPS_INSTALLED";
+
+ /**
+ * Explaining that the number of apps is an estimation
+ */
+ public static final String ADMIN_ACTION_APPS_COUNT_ESTIMATED =
+ PREFIX + "ADMIN_ACTION_APPS_COUNT_ESTIMATED";
+
+ /**
+ * Indicating the minimum number of apps that a label refers to
+ */
+ public static final String ADMIN_ACTIONS_APPS_COUNT_MINIMUM =
+ PREFIX + "ADMIN_ACTIONS_APPS_COUNT_MINIMUM";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the device's location
+ */
+ public static final String ADMIN_ACTION_ACCESS_LOCATION =
+ PREFIX + "ADMIN_ACTION_ACCESS_LOCATION";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the microphone
+ */
+ public static final String ADMIN_ACTION_ACCESS_MICROPHONE =
+ PREFIX + "ADMIN_ACTION_ACCESS_MICROPHONE";
+
+ /**
+ * Indicate that the admin granted one or more apps access to the camera
+ */
+ public static final String ADMIN_ACTION_ACCESS_CAMERA =
+ PREFIX + "ADMIN_ACTION_ACCESS_CAMERA";
+
+ /**
+ * Indicate that the admin set one or more apps as defaults for common actions
+ */
+ public static final String ADMIN_ACTION_SET_DEFAULT_APPS =
+ PREFIX + "ADMIN_ACTION_SET_DEFAULT_APPS";
+
+ /**
+ * Indicate the number of apps that a label refers to
+ */
+ public static final String ADMIN_ACTIONS_APPS_COUNT =
+ PREFIX + "ADMIN_ACTIONS_APPS_COUNT";
+
+ /**
+ * Indicate that the current input method was set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_CURRENT_INPUT_METHOD =
+ PREFIX + "ADMIN_ACTION_SET_CURRENT_INPUT_METHOD";
+
+ /**
+ * The input method set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_INPUT_METHOD_NAME =
+ PREFIX + "ADMIN_ACTION_SET_INPUT_METHOD_NAME";
+
+ /**
+ * Indicate that a global HTTP proxy was set by the admin
+ */
+ public static final String ADMIN_ACTION_SET_HTTP_PROXY =
+ PREFIX + "ADMIN_ACTION_SET_HTTP_PROXY";
+
+ /**
+ * Summary for Enterprise Privacy settings, explaining what the user can expect to find
+ * under it
+ */
+ public static final String WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY =
+ PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY";
+
+ /**
+ * Setting on privacy settings screen that will show work policy info
+ */
+ public static final String WORK_PROFILE_PRIVACY_POLICY_INFO =
+ PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO";
+
+ /**
+ * Search keywords for connected work and personal apps
+ */
+ public static final String CONNECTED_APPS_SEARCH_KEYWORDS =
+ PREFIX + "CONNECTED_APPS_SEARCH_KEYWORDS";
+
+ /**
+ * Work profile unification keywords
+ */
+ public static final String WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS =
+ PREFIX + "WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS";
+
+ /**
+ * Accounts keywords
+ */
+ public static final String ACCOUNTS_SEARCH_KEYWORDS =
+ PREFIX + "ACCOUNTS_SEARCH_KEYWORDS";
+
+ /**
+ * Summary for settings preference disabled by administrator
+ */
+ public static final String CONTROLLED_BY_ADMIN_SUMMARY =
+ PREFIX + "CONTROLLED_BY_ADMIN_SUMMARY";
+
+ /**
+ * User label for a work profile
+ */
+ public static final String WORK_PROFILE_USER_LABEL = PREFIX + "WORK_PROFILE_USER_LABEL";
+
+ /**
+ * Header for items under the work user
+ */
+ public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER";
+
+ /**
+ * Header for items under the personal user
+ */
+ public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal";
+
+ /**
+ * @hide
+ */
+ static Set<String> buildStringsSet() {
+ Set<String> strings = new HashSet<>();
+ strings.add(FACE_SETTINGS_FOR_WORK_TITLE);
+ strings.add(WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE);
+ strings.add(WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK);
+ strings.add(WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE);
+ strings.add(WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE);
+ strings.add(WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE);
+ strings.add(WORK_PROFILE_LOCK_ATTEMPTS_FAILED);
+ strings.add(ACCESSIBILITY_CATEGORY_WORK);
+ strings.add(ACCESSIBILITY_CATEGORY_PERSONAL);
+ strings.add(ACCESSIBILITY_WORK_ACCOUNT_TITLE);
+ strings.add(ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE);
+ strings.add(WORK_PROFILE_LOCATION_SWITCH_TITLE);
+ strings.add(SET_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(SET_WORK_PROFILE_PIN_HEADER);
+ strings.add(SET_WORK_PROFILE_PATTERN_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PIN_HEADER);
+ strings.add(CONFIRM_WORK_PROFILE_PATTERN_HEADER);
+ strings.add(REENTER_WORK_PROFILE_PASSWORD_HEADER);
+ strings.add(REENTER_WORK_PROFILE_PIN_HEADER);
+ strings.add(WORK_PROFILE_CONFIRM_PATTERN);
+ strings.add(WORK_PROFILE_CONFIRM_PIN);
+ strings.add(WORK_PROFILE_PASSWORD_REQUIRED);
+ strings.add(WORK_PROFILE_SECURITY_TITLE);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_TITLE);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_SUMMARY);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_DETAIL);
+ strings.add(WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT);
+ strings.add(WORK_PROFILE_KEYBOARDS_AND_TOOLS);
+ strings.add(WORK_PROFILE_NOT_AVAILABLE);
+ strings.add(WORK_PROFILE_SETTING);
+ strings.add(WORK_PROFILE_SETTING_ON_SUMMARY);
+ strings.add(WORK_PROFILE_SETTING_OFF_SUMMARY);
+ strings.add(REMOVE_WORK_PROFILE);
+ strings.add(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+ strings.add(WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING);
+ strings.add(WORK_PROFILE_CONFIRM_REMOVE_TITLE);
+ strings.add(WORK_PROFILE_CONFIRM_REMOVE_MESSAGE);
+ strings.add(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS);
+ strings.add(WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER);
+ strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE);
+ strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY);
+ strings.add(WORK_PROFILE_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_ALARM_RINGTONE_TITLE);
+ strings.add(WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY);
+ strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE);
+ strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE);
+ strings.add(WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER);
+ strings.add(WORK_PROFILE_LOCKED_NOTIFICATION_TITLE);
+ strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE);
+ strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY);
+ strings.add(WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED);
+ strings.add(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE);
+ strings.add(CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA);
+ strings.add(ONLY_CONNECT_TRUSTED_APPS);
+ strings.add(HOW_TO_DISCONNECT_APPS);
+ strings.add(CONNECT_APPS_DIALOG_TITLE);
+ strings.add(CONNECT_APPS_DIALOG_SUMMARY);
+ strings.add(APP_CAN_ACCESS_PERSONAL_DATA);
+ strings.add(APP_CAN_ACCESS_PERSONAL_PERMISSIONS);
+ strings.add(INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT);
+ strings.add(INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT);
+ strings.add(WORK_PROFILE_MANAGED_BY);
+ strings.add(MANAGED_BY);
+ strings.add(WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING);
+ strings.add(DISABLED_BY_IT_ADMIN_TITLE);
+ strings.add(CONTACT_YOUR_IT_ADMIN);
+ strings.add(WORK_PROFILE_ADMIN_POLICIES_WARNING);
+ strings.add(USER_ADMIN_POLICIES_WARNING);
+ strings.add(DEVICE_ADMIN_POLICIES_WARNING);
+ strings.add(WORK_PROFILE_OFF_CONDITION_TITLE);
+ strings.add(MANAGED_PROFILE_SETTINGS_TITLE);
+ strings.add(WORK_PROFILE_CONTACT_SEARCH_TITLE);
+ strings.add(WORK_PROFILE_CONTACT_SEARCH_SUMMARY);
+ strings.add(CROSS_PROFILE_CALENDAR_TITLE);
+ strings.add(CROSS_PROFILE_CALENDAR_SUMMARY);
+ strings.add(ALWAYS_ON_VPN_PERSONAL_PROFILE);
+ strings.add(ALWAYS_ON_VPN_DEVICE);
+ strings.add(ALWAYS_ON_VPN_WORK_PROFILE);
+ strings.add(CA_CERTS_PERSONAL_PROFILE);
+ strings.add(CA_CERTS_WORK_PROFILE);
+ strings.add(CA_CERTS_DEVICE);
+ strings.add(ADMIN_CAN_LOCK_DEVICE);
+ strings.add(ADMIN_CAN_WIPE_DEVICE);
+ strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE);
+ strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE);
+ strings.add(DEVICE_MANAGED_WITHOUT_NAME);
+ strings.add(DEVICE_MANAGED_WITH_NAME);
+ strings.add(WORK_PROFILE_APP_SUBTEXT);
+ strings.add(PERSONAL_PROFILE_APP_SUBTEXT);
+ strings.add(FINGERPRINT_FOR_WORK);
+ strings.add(FACE_UNLOCK_DISABLED);
+ strings.add(FINGERPRINT_UNLOCK_DISABLED);
+ strings.add(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION);
+ strings.add(PIN_RECENTLY_USED);
+ strings.add(PASSWORD_RECENTLY_USED);
+ strings.add(MANAGE_DEVICE_ADMIN_APPS);
+ strings.add(NUMBER_OF_DEVICE_ADMINS_NONE);
+ strings.add(NUMBER_OF_DEVICE_ADMINS);
+ strings.add(FORGOT_PASSWORD_TITLE);
+ strings.add(FORGOT_PASSWORD_TEXT);
+ strings.add(ERROR_MOVE_DEVICE_ADMIN);
+ strings.add(DEVICE_ADMIN_SETTINGS_TITLE);
+ strings.add(REMOVE_DEVICE_ADMIN);
+ strings.add(UNINSTALL_DEVICE_ADMIN);
+ strings.add(REMOVE_AND_UNINSTALL_DEVICE_ADMIN);
+ strings.add(SELECT_DEVICE_ADMIN_APPS);
+ strings.add(NO_DEVICE_ADMINS);
+ strings.add(ACTIVATE_DEVICE_ADMIN_APP);
+ strings.add(ACTIVATE_THIS_DEVICE_ADMIN_APP);
+ strings.add(ACTIVATE_DEVICE_ADMIN_APP_TITLE);
+ strings.add(NEW_DEVICE_ADMIN_WARNING);
+ strings.add(NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED);
+ strings.add(ACTIVE_DEVICE_ADMIN_WARNING);
+ strings.add(SET_PROFILE_OWNER_TITLE);
+ strings.add(SET_PROFILE_OWNER_DIALOG_TITLE);
+ strings.add(SET_PROFILE_OWNER_POSTSETUP_WARNING);
+ strings.add(OTHER_OPTIONS_DISABLED_BY_ADMIN);
+ strings.add(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION);
+ strings.add(IT_ADMIN_POLICY_DISABLING_INFO_URL);
+ strings.add(SHARE_REMOTE_BUGREPORT_DIALOG_TITLE);
+ strings.add(SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT);
+ strings.add(SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT);
+ strings.add(SHARING_REMOTE_BUGREPORT_MESSAGE);
+ strings.add(MANAGED_DEVICE_INFO);
+ strings.add(MANAGED_DEVICE_INFO_SUMMARY);
+ strings.add(MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME);
+ strings.add(ENTERPRISE_PRIVACY_HEADER);
+ strings.add(INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE);
+ strings.add(CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE);
+ strings.add(YOUR_ACCESS_TO_THIS_DEVICE_TITLE);
+ strings.add(ADMIN_CAN_SEE_WORK_DATA_WARNING);
+ strings.add(ADMIN_CAN_SEE_APPS_WARNING);
+ strings.add(ADMIN_CAN_SEE_USAGE_WARNING);
+ strings.add(ADMIN_CAN_SEE_NETWORK_LOGS_WARNING);
+ strings.add(ADMIN_CAN_SEE_BUG_REPORT_WARNING);
+ strings.add(ADMIN_CAN_SEE_SECURITY_LOGS_WARNING);
+ strings.add(ADMIN_ACTION_NONE);
+ strings.add(ADMIN_ACTION_APPS_INSTALLED);
+ strings.add(ADMIN_ACTION_APPS_COUNT_ESTIMATED);
+ strings.add(ADMIN_ACTIONS_APPS_COUNT_MINIMUM);
+ strings.add(ADMIN_ACTION_ACCESS_LOCATION);
+ strings.add(ADMIN_ACTION_ACCESS_MICROPHONE);
+ strings.add(ADMIN_ACTION_ACCESS_CAMERA);
+ strings.add(ADMIN_ACTION_SET_DEFAULT_APPS);
+ strings.add(ADMIN_ACTIONS_APPS_COUNT);
+ strings.add(ADMIN_ACTION_SET_CURRENT_INPUT_METHOD);
+ strings.add(ADMIN_ACTION_SET_INPUT_METHOD_NAME);
+ strings.add(ADMIN_ACTION_SET_HTTP_PROXY);
+ strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY);
+ strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO);
+ strings.add(CONNECTED_APPS_SEARCH_KEYWORDS);
+ strings.add(WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS);
+ strings.add(ACCOUNTS_SEARCH_KEYWORDS);
+ strings.add(CONTROLLED_BY_ADMIN_SUMMARY);
+ strings.add(WORK_PROFILE_USER_LABEL);
+ strings.add(WORK_CATEGORY_HEADER);
+ strings.add(PERSONAL_CATEGORY_HEADER);
+ return strings;
+ }
+ }
+
+ /**
+ * Class containing the identifiers used to update device management-related system strings
* in the Launcher package.
*
* @hide
*/
public static final class Launcher {
- private Launcher(){}
+ private Launcher() {
+ }
private static final String PREFIX = "Launcher.";
@@ -576,6 +2039,7 @@
private SystemUi() {
}
+
private static final String PREFIX = "SystemUi.";
/**
@@ -649,9 +2113,9 @@
PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING";
/**
- * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
+ * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
* admin.
- */
+ */
public static final String QS_MSG_WORK_PROFILE_NETWORK =
PREFIX + "QS_MSG_WORK_PROFILE_NETWORK";
@@ -1413,5 +2877,120 @@
return strings;
}
}
+
+ /**
+ * Class containing the identifiers used to update device management-related system strings
+ * in the PermissionController module.
+ */
+ public static final class PermissionController {
+
+ private PermissionController() {
+ }
+
+ private static final String PREFIX = "PermissionController.";
+
+ /**
+ * Title for settings page to show default apps for work.
+ */
+ public static final String WORK_PROFILE_DEFAULT_APPS_TITLE =
+ PREFIX + "WORK_PROFILE_DEFAULT_APPS_TITLE";
+
+ /**
+ * Summary indicating that a home role holder app is missing work profile support.
+ */
+ public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE =
+ PREFIX + "HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the background access is denied by an
+ * admin.
+ */
+ public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the background access is enabled by
+ * an admin.
+ */
+ public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Summary of a permission switch in Settings when the foreground access is enabled by
+ * an admin.
+ */
+ public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE =
+ PREFIX + "FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE";
+
+ /**
+ * Body of the notification shown to notify the user that the location permission has
+ * been granted to an app, accepts app name as a param.
+ */
+ public static final String LOCATION_AUTO_GRANTED_MESSAGE =
+ PREFIX + "LOCATION_AUTO_GRANTED_MESSAGE";
+
+ /**
+ * @hide
+ */
+ static Set<String> buildStringsSet() {
+ Set<String> strings = new HashSet<>();
+ strings.add(WORK_PROFILE_DEFAULT_APPS_TITLE);
+ strings.add(HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE);
+ strings.add(BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE);
+ strings.add(BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+ strings.add(FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE);
+ strings.add(LOCATION_AUTO_GRANTED_MESSAGE);
+ return strings;
+ }
+ }
+
+ /**
+ * Class containing the identifiers used to update device management-related system strings
+ * in the Dialer app.
+ */
+ public static final class Dialer {
+
+ private Dialer() {
+ }
+
+ private static final String PREFIX = "Dialer.";
+
+ /**
+ * The title of the in-call notification for an incoming work call.
+ */
+ public static final String NOTIFICATION_INCOMING_WORK_CALL_TITLE =
+ PREFIX + "NOTIFICATION_INCOMING_WORK_CALL_TITLE";
+
+ /**
+ * The title of the in-call notification for an ongoing work call.
+ */
+ public static final String NOTIFICATION_ONGOING_WORK_CALL_TITLE =
+ PREFIX + "NOTIFICATION_ONGOING_WORK_CALL_TITLE";
+
+ /**
+ * Missed call notification label, used when there's exactly one missed call from work
+ * contact.
+ */
+ public static final String NOTIFICATION_MISSED_WORK_CALL_TITLE =
+ PREFIX + "NOTIFICATION_MISSED_WORK_CALL_TITLE";
+
+ /**
+ * Label for notification indicating that call is being made over wifi.
+ */
+ public static final String NOTIFICATION_WIFI_WORK_CALL_LABEL =
+ PREFIX + "NOTIFICATION_WIFI_WORK_CALL_LABEL";
+
+ /**
+ * @hide
+ */
+ static Set<String> buildStringsSet() {
+ Set<String> strings = new HashSet<>();
+ strings.add(NOTIFICATION_INCOMING_WORK_CALL_TITLE);
+ strings.add(NOTIFICATION_ONGOING_WORK_CALL_TITLE);
+ strings.add(NOTIFICATION_MISSED_WORK_CALL_TITLE);
+ strings.add(NOTIFICATION_WIFI_WORK_CALL_LABEL);
+ return strings;
+ }
+ }
}
}
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 8c232c0..1f7ae4a 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.stats.devicepolicy.DevicePolicyEnums;
import java.util.Locale;
@@ -52,6 +53,7 @@
@SuppressLint("UseIcu")
@Nullable private final Locale mLocale;
private final boolean mDeviceOwnerCanGrantSensorsPermissions;
+ @NonNull private final PersistableBundle mAdminExtras;
private FullyManagedDeviceProvisioningParams(
@NonNull ComponentName deviceAdminComponentName,
@@ -60,7 +62,8 @@
@Nullable String timeZone,
long localTime,
@Nullable @SuppressLint("UseIcu") Locale locale,
- boolean deviceOwnerCanGrantSensorsPermissions) {
+ boolean deviceOwnerCanGrantSensorsPermissions,
+ @NonNull PersistableBundle adminExtras) {
this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName);
this.mOwnerName = requireNonNull(ownerName);
this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
@@ -69,6 +72,7 @@
this.mLocale = locale;
this.mDeviceOwnerCanGrantSensorsPermissions =
deviceOwnerCanGrantSensorsPermissions;
+ this.mAdminExtras = adminExtras;
}
private FullyManagedDeviceProvisioningParams(
@@ -78,14 +82,16 @@
@Nullable String timeZone,
long localTime,
@Nullable String localeStr,
- boolean deviceOwnerCanGrantSensorsPermissions) {
+ boolean deviceOwnerCanGrantSensorsPermissions,
+ @Nullable PersistableBundle adminExtras) {
this(deviceAdminComponentName,
ownerName,
leaveAllSystemAppsEnabled,
timeZone,
localTime,
getLocale(localeStr),
- deviceOwnerCanGrantSensorsPermissions);
+ deviceOwnerCanGrantSensorsPermissions,
+ adminExtras);
}
@Nullable
@@ -151,6 +157,15 @@
}
/**
+ * Returns a copy of the admin extras bundle.
+ *
+ * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+ */
+ public @NonNull PersistableBundle getAdminExtras() {
+ return new PersistableBundle(mAdminExtras);
+ }
+
+ /**
* Logs the provisioning params using {@link DevicePolicyEventLogger}.
*
* @hide
@@ -188,6 +203,7 @@
@Nullable private Locale mLocale;
// Default to allowing control over sensor permission grants.
boolean mDeviceOwnerCanGrantSensorsPermissions = true;
+ @NonNull private PersistableBundle mAdminExtras;
/**
* Initialize a new {@link Builder} to construct a
@@ -262,6 +278,17 @@
}
/**
+ * Sets a {@link PersistableBundle} that contains admin-specific extras.
+ */
+ @NonNull
+ public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+ mAdminExtras = adminExtras != null
+ ? new PersistableBundle(adminExtras)
+ : new PersistableBundle();
+ return this;
+ }
+
+ /**
* Combines all of the attributes that have been set on this {@code Builder}
*
* @return a new {@link FullyManagedDeviceProvisioningParams} object.
@@ -275,7 +302,8 @@
mTimeZone,
mLocalTime,
mLocale,
- mDeviceOwnerCanGrantSensorsPermissions);
+ mDeviceOwnerCanGrantSensorsPermissions,
+ mAdminExtras);
}
}
@@ -298,6 +326,7 @@
+ ", mLocale=" + (mLocale == null ? "null" : mLocale)
+ ", mDeviceOwnerCanGrantSensorsPermissions="
+ mDeviceOwnerCanGrantSensorsPermissions
+ + ", mAdminExtras=" + mAdminExtras
+ '}';
}
@@ -310,6 +339,7 @@
dest.writeLong(mLocalTime);
dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions);
+ dest.writePersistableBundle(mAdminExtras);
}
@NonNull
@@ -324,6 +354,7 @@
long localtime = in.readLong();
String locale = in.readString();
boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean();
+ PersistableBundle adminExtras = in.readPersistableBundle();
return new FullyManagedDeviceProvisioningParams(
componentName,
@@ -332,7 +363,8 @@
timeZone,
localtime,
locale,
- deviceOwnerCanGrantSensorsPermissions);
+ deviceOwnerCanGrantSensorsPermissions,
+ adminExtras);
}
@Override
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 927ee0c..0e1caca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -272,6 +272,7 @@
int getLogoutUserId();
List<UserHandle> getSecondaryUsers(in ComponentName who);
void acknowledgeNewUserDisclaimer();
+ boolean isNewUserDisclaimerAcknowledged();
void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
@@ -554,6 +555,9 @@
void resetDrawables(in String[] drawableIds);
ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource);
+ boolean isDpcDownloaded();
+ void setDpcDownloaded(boolean downloaded);
+
void setStrings(in List<DevicePolicyStringResource> strings);
void resetStrings(in String[] stringIds);
ParcelableResource getString(String stringId);
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index ccbef73..f91d60a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -23,8 +23,10 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.stats.devicepolicy.DevicePolicyEnums;
/**
@@ -49,7 +51,7 @@
private final boolean mLeaveAllSystemAppsEnabled;
private final boolean mOrganizationOwnedProvisioning;
private final boolean mKeepAccountOnMigration;
-
+ @NonNull private final PersistableBundle mAdminExtras;
private ManagedProfileProvisioningParams(
@NonNull ComponentName profileAdminComponentName,
@@ -58,7 +60,8 @@
@Nullable Account accountToMigrate,
boolean leaveAllSystemAppsEnabled,
boolean organizationOwnedProvisioning,
- boolean keepAccountOnMigration) {
+ boolean keepAccountOnMigration,
+ @NonNull PersistableBundle adminExtras) {
this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName);
this.mOwnerName = requireNonNull(ownerName);
this.mProfileName = profileName;
@@ -66,6 +69,7 @@
this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning;
this.mKeepAccountOnMigration = keepAccountOnMigration;
+ this.mAdminExtras = adminExtras;
}
/**
@@ -124,6 +128,15 @@
}
/**
+ * Returns a copy of the admin extras bundle.
+ *
+ * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
+ */
+ public @NonNull PersistableBundle getAdminExtras() {
+ return new PersistableBundle(mAdminExtras);
+ }
+
+ /**
* Logs the provisioning params using {@link DevicePolicyEventLogger}.
*
* @hide
@@ -160,6 +173,7 @@
private boolean mLeaveAllSystemAppsEnabled;
private boolean mOrganizationOwnedProvisioning;
private boolean mKeepingAccountOnMigration;
+ @Nullable private PersistableBundle mAdminExtras;
/**
* Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}.
@@ -235,6 +249,17 @@
}
/**
+ * Sets a {@link Bundle} that contains admin-specific extras.
+ */
+ @NonNull
+ public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) {
+ mAdminExtras = adminExtras != null
+ ? new PersistableBundle(adminExtras)
+ : new PersistableBundle();
+ return this;
+ }
+
+ /**
* Combines all of the attributes that have been set on this {@code Builder}.
*
* @return a new {@link ManagedProfileProvisioningParams} object.
@@ -248,7 +273,8 @@
mAccountToMigrate,
mLeaveAllSystemAppsEnabled,
mOrganizationOwnedProvisioning,
- mKeepingAccountOnMigration);
+ mKeepingAccountOnMigration,
+ mAdminExtras);
}
}
@@ -270,6 +296,7 @@
+ ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled
+ ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning
+ ", mKeepAccountOnMigration=" + mKeepAccountOnMigration
+ + ", mAdminExtras=" + mAdminExtras
+ '}';
}
@@ -282,6 +309,7 @@
dest.writeBoolean(mLeaveAllSystemAppsEnabled);
dest.writeBoolean(mOrganizationOwnedProvisioning);
dest.writeBoolean(mKeepAccountOnMigration);
+ dest.writePersistableBundle(mAdminExtras);
}
public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR =
@@ -295,6 +323,7 @@
boolean leaveAllSystemAppsEnabled = in.readBoolean();
boolean organizationOwnedProvisioning = in.readBoolean();
boolean keepAccountMigrated = in.readBoolean();
+ PersistableBundle adminExtras = in.readPersistableBundle();
return new ManagedProfileProvisioningParams(
componentName,
@@ -303,7 +332,8 @@
account,
leaveAllSystemAppsEnabled,
organizationOwnedProvisioning,
- keepAccountMigrated);
+ keepAccountMigrated,
+ adminExtras);
}
@Override
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index dba3628..0b1b166 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -175,7 +175,7 @@
* <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
* drawable was not found or could not be loaded.</p>
*/
- @NonNull
+ @Nullable
public Drawable getDrawable(
Context context,
int density,
@@ -200,7 +200,7 @@
* <p>Returns the default string by calling {@code defaultStringLoader} if the updated
* string was not found or could not be loaded.</p>
*/
- @NonNull
+ @Nullable
public String getString(
Context context,
@NonNull Callable<String> defaultStringLoader) {
@@ -267,17 +267,11 @@
/**
* returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
*/
- @NonNull
+ @Nullable
public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) {
try {
Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-
- Drawable drawable = defaultDrawableLoader.call();
- Objects.requireNonNull(drawable, "defaultDrawable can't be null");
-
- return drawable;
- } catch (NullPointerException rethrown) {
- throw rethrown;
+ return defaultDrawableLoader.call();
} catch (Exception e) {
throw new RuntimeException("Couldn't load default drawable: ", e);
}
@@ -286,17 +280,11 @@
/**
* returns the {@link String} loaded from calling {@code defaultStringLoader}.
*/
- @NonNull
+ @Nullable
public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) {
try {
Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
-
- String string = defaultStringLoader.call();
- Objects.requireNonNull(string, "defaultString can't be null");
-
- return string;
- } catch (NullPointerException rethrown) {
- throw rethrown;
+ return defaultStringLoader.call();
} catch (Exception e) {
throw new RuntimeException("Couldn't load default string: ", e);
}
diff --git a/core/java/android/app/admin/WifiSsidPolicy.java b/core/java/android/app/admin/WifiSsidPolicy.java
index 3715017..a7046d4 100644
--- a/core/java/android/app/admin/WifiSsidPolicy.java
+++ b/core/java/android/app/admin/WifiSsidPolicy.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.net.wifi.WifiSsid;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -70,25 +71,25 @@
public @interface WifiSsidPolicyType {}
private @WifiSsidPolicyType int mPolicyType;
- private ArraySet<String> mSsids;
+ private ArraySet<WifiSsid> mSsids;
- private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<String> ssids) {
+ private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<WifiSsid> ssids) {
mPolicyType = policyType;
mSsids = new ArraySet<>(ssids);
}
private WifiSsidPolicy(Parcel in) {
mPolicyType = in.readInt();
- mSsids = (ArraySet<String>) in.readArraySet(null);
+ mSsids = (ArraySet<WifiSsid>) in.readArraySet(null);
}
/**
* Create the allowlist Wi-Fi SSID Policy.
*
- * @param ssids allowlist of SSIDs in UTF-8 without double quotes format
+ * @param ssids allowlist of {@link WifiSsid}
* @throws IllegalArgumentException if the input ssids list is empty
*/
@NonNull
- public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<String> ssids) {
+ public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<WifiSsid> ssids) {
if (ssids.isEmpty()) {
throw new IllegalArgumentException("SSID list cannot be empty");
}
@@ -98,11 +99,11 @@
/**
* Create the denylist Wi-Fi SSID Policy.
*
- * @param ssids denylist of SSIDs in UTF-8 without double quotes format
+ * @param ssids denylist of {@link WifiSsid}
* @throws IllegalArgumentException if the input ssids list is empty
*/
@NonNull
- public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<String> ssids) {
+ public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<WifiSsid> ssids) {
if (ssids.isEmpty()) {
throw new IllegalArgumentException("SSID list cannot be empty");
}
@@ -110,10 +111,10 @@
}
/**
- * Returns the set of SSIDs in UTF-8 without double quotes format.
+ * Returns the set of {@link WifiSsid}
*/
@NonNull
- public Set<String> getSsids() {
+ public Set<WifiSsid> getSsids() {
return mSsids;
}
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/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/cloudsearch/SearchRequest.aidl
similarity index 82%
rename from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
rename to core/java/android/app/cloudsearch/SearchRequest.aidl
index cb602d79..9f2cdb8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/cloudsearch/SearchRequest.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.app.cloudsearch;
-parcelable NetworkStateSnapshot;
+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/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/cloudsearch/SearchResponse.aidl
similarity index 82%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/cloudsearch/SearchResponse.aidl
index cb602d79..2064d11 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/cloudsearch/SearchResponse.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.app.cloudsearch;
-parcelable NetworkStateSnapshot;
+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/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/cloudsearch/SearchResult.aidl
similarity index 82%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/cloudsearch/SearchResult.aidl
index cb602d79..daebfbf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/cloudsearch/SearchResult.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.app.cloudsearch;
-parcelable NetworkStateSnapshot;
+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/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 8e98535..78f51be 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.net.Uri;
@@ -131,6 +132,9 @@
@Nullable
private final AppWidgetProviderInfo mWidget;
+ @Nullable
+ private final SmartspaceDefaultUiTemplateData mTemplateData;
+
public static final int FEATURE_UNDEFINED = 0;
public static final int FEATURE_WEATHER = 1;
public static final int FEATURE_CALENDAR = 2;
@@ -189,6 +193,32 @@
public @interface FeatureType {
}
+ public static final int UI_TEMPLATE_UNDEFINED = 0;
+ public static final int UI_TEMPLATE_DEFAULT = 1;
+ public static final int UI_TEMPLATE_SUB_IMAGE = 2;
+ public static final int UI_TEMPLATE_SUB_LIST = 3;
+ public static final int UI_TEMPLATE_CAROUSEL = 4;
+ public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5;
+ public static final int UI_TEMPLATE_COMBINED_CARDS = 6;
+ public static final int UI_TEMPLATE_SUB_CARD = 7;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"UI_TEMPLATE_"}, value = {
+ UI_TEMPLATE_UNDEFINED,
+ UI_TEMPLATE_DEFAULT,
+ UI_TEMPLATE_SUB_IMAGE,
+ UI_TEMPLATE_SUB_LIST,
+ UI_TEMPLATE_CAROUSEL,
+ UI_TEMPLATE_HEAD_TO_HEAD,
+ UI_TEMPLATE_COMBINED_CARDS,
+ UI_TEMPLATE_SUB_CARD
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UiTemplateType {
+ }
+
private SmartspaceTarget(Parcel in) {
this.mSmartspaceTargetId = in.readString();
this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
@@ -207,6 +237,7 @@
this.mAssociatedSmartspaceTargetId = in.readString();
this.mSliceUri = in.readTypedObject(Uri.CREATOR);
this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+ this.mTemplateData = in.readTypedObject(SmartspaceDefaultUiTemplateData.CREATOR);
}
private SmartspaceTarget(String smartspaceTargetId,
@@ -217,7 +248,7 @@
boolean shouldShowExpanded, String sourceNotificationKey,
ComponentName componentName, UserHandle userHandle,
String associatedSmartspaceTargetId, Uri sliceUri,
- AppWidgetProviderInfo widget) {
+ AppWidgetProviderInfo widget, SmartspaceDefaultUiTemplateData templateData) {
mSmartspaceTargetId = smartspaceTargetId;
mHeaderAction = headerAction;
mBaseAction = baseAction;
@@ -235,6 +266,7 @@
mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
mSliceUri = sliceUri;
mWidget = widget;
+ mTemplateData = templateData;
}
/**
@@ -371,6 +403,14 @@
}
/**
+ * Returns the UI template data.
+ */
+ @Nullable
+ public SmartspaceDefaultUiTemplateData getTemplateData() {
+ return mTemplateData;
+ }
+
+ /**
* @see Parcelable.Creator
*/
@NonNull
@@ -405,6 +445,7 @@
dest.writeString(this.mAssociatedSmartspaceTargetId);
dest.writeTypedObject(this.mSliceUri, flags);
dest.writeTypedObject(this.mWidget, flags);
+ dest.writeTypedObject(this.mTemplateData, flags);
}
@Override
@@ -432,6 +473,7 @@
+ ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+ ", mSliceUri=" + mSliceUri
+ ", mWidget=" + mWidget
+ + ", mTemplateData=" + mTemplateData
+ '}';
}
@@ -457,7 +499,8 @@
&& Objects.equals(mAssociatedSmartspaceTargetId,
that.mAssociatedSmartspaceTargetId)
&& Objects.equals(mSliceUri, that.mSliceUri)
- && Objects.equals(mWidget, that.mWidget);
+ && Objects.equals(mWidget, that.mWidget)
+ && Objects.equals(mTemplateData, that.mTemplateData);
}
@Override
@@ -465,7 +508,7 @@
return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
- mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
}
/**
@@ -476,6 +519,9 @@
@SystemApi
public static final class Builder {
private final String mSmartspaceTargetId;
+ private final ComponentName mComponentName;
+ private final UserHandle mUserHandle;
+
private SmartspaceAction mHeaderAction;
private SmartspaceAction mBaseAction;
private long mCreationTimeMillis;
@@ -487,11 +533,10 @@
private boolean mSensitive;
private boolean mShouldShowExpanded;
private String mSourceNotificationKey;
- private final ComponentName mComponentName;
- private final UserHandle mUserHandle;
private String mAssociatedSmartspaceTargetId;
private Uri mSliceUri;
private AppWidgetProviderInfo mWidget;
+ private SmartspaceDefaultUiTemplateData mTemplateData;
/**
* A builder for {@link SmartspaceTarget}.
@@ -640,6 +685,16 @@
}
/**
+ * Sets the UI template data.
+ */
+ @NonNull
+ public Builder setTemplateData(
+ @Nullable SmartspaceDefaultUiTemplateData templateData) {
+ mTemplateData = templateData;
+ return this;
+ }
+
+ /**
* Builds a new {@link SmartspaceTarget}.
*
* @throws IllegalStateException when non null fields are set as null.
@@ -655,7 +710,7 @@
mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
mSourceNotificationKey, mComponentName, mUserHandle,
- mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
}
}
}
diff --git a/core/java/android/app/smartspace/SmartspaceUtils.java b/core/java/android/app/smartspace/SmartspaceUtils.java
new file mode 100644
index 0000000..f058ffa
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.smartspace;
+
+import android.annotation.Nullable;
+
+/**
+ * Utilities for Smartspace data.
+ *
+ * @hide
+ */
+public final class SmartspaceUtils {
+
+ private SmartspaceUtils() {
+ }
+
+ /** Returns true if the passed-in {@link CharSequence}s are equal. */
+ public static boolean isEqual(@Nullable CharSequence cs1, @Nullable CharSequence cs2) {
+ if ((cs1 == null && cs2 != null) || (cs1 != null && cs2 == null)) return false;
+ if (cs1 == null && cs2 == null) return true;
+ return cs1.toString().contentEquals(cs2);
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
new file mode 100644
index 0000000..c4c4fde
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the carousel Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCarouselUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Lists of {@link CarouselItem}. */
+ @NonNull
+ private final List<CarouselItem> mCarouselItems;
+
+ /** Tap action for the entire carousel secondary card, including the blank space */
+ @Nullable
+ private final SmartspaceTapAction mCarouselAction;
+
+ SmartspaceCarouselUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mCarouselItems = in.createTypedArrayList(CarouselItem.CREATOR);
+ mCarouselAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceCarouselUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<CarouselItem> carouselItems,
+ @Nullable SmartspaceTapAction carouselAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mCarouselItems = carouselItems;
+ mCarouselAction = carouselAction;
+ }
+
+ @NonNull
+ public List<CarouselItem> getCarouselItems() {
+ return mCarouselItems;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getCarouselAction() {
+ return mCarouselAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceCarouselUiTemplateData> CREATOR =
+ new Creator<SmartspaceCarouselUiTemplateData>() {
+ @Override
+ public SmartspaceCarouselUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceCarouselUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceCarouselUiTemplateData[] newArray(int size) {
+ return new SmartspaceCarouselUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedList(mCarouselItems);
+ out.writeTypedObject(mCarouselAction, flags);
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceCarouselUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceCarouselUiTemplateData that = (SmartspaceCarouselUiTemplateData) o;
+ return mCarouselItems.equals(that.mCarouselItems) && Objects.equals(mCarouselAction,
+ that.mCarouselAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCarouselItems, mCarouselAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceCarouselUiTemplateData{"
+ + "mCarouselItems=" + mCarouselItems
+ + ", mCarouselActions=" + mCarouselAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceCarouselUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<CarouselItem> mCarouselItems;
+ private SmartspaceTapAction mCarouselAction;
+
+ /**
+ * A builder for {@link SmartspaceCarouselUiTemplateData}.
+ */
+ public Builder(@NonNull List<CarouselItem> carouselItems) {
+ super(SmartspaceTarget.UI_TEMPLATE_CAROUSEL);
+ mCarouselItems = Objects.requireNonNull(carouselItems);
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction carouselAction) {
+ mCarouselAction = carouselAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceCarouselUiTemplateData instance.
+ *
+ * @throws IllegalStateException if the carousel data is invalid.
+ */
+ @NonNull
+ public SmartspaceCarouselUiTemplateData build() {
+ if (mCarouselItems.isEmpty()) {
+ throw new IllegalStateException("Carousel data is empty");
+ }
+ return new SmartspaceCarouselUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mCarouselItems,
+ mCarouselAction);
+ }
+ }
+
+ /** Holds all the relevant data needed to render a carousel item. */
+ public static final class CarouselItem implements Parcelable {
+
+ /** Text which is above the image item. */
+ @Nullable
+ private final CharSequence mUpperText;
+
+ /** Image item. Can be empty. */
+ @Nullable
+ private final SmartspaceIcon mImage;
+
+ /** Text which is under the image item. */
+ @Nullable
+ private final CharSequence mLowerText;
+
+ /**
+ * Tap action for this {@link CarouselItem} instance. {@code mCarouselAction} is used if not
+ * being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mTapAction;
+
+ CarouselItem(@NonNull Parcel in) {
+ mUpperText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mImage = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mLowerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private CarouselItem(@Nullable CharSequence upperText, @Nullable SmartspaceIcon image,
+ @Nullable CharSequence lowerText, @Nullable SmartspaceTapAction tapAction) {
+ mUpperText = upperText;
+ mImage = image;
+ mLowerText = lowerText;
+ mTapAction = tapAction;
+ }
+
+ @Nullable
+ public CharSequence getUpperText() {
+ return mUpperText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getImage() {
+ return mImage;
+ }
+
+ @Nullable
+ public CharSequence getLowerText() {
+ return mLowerText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getTapAction() {
+ return mTapAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<CarouselItem> CREATOR =
+ new Creator<CarouselItem>() {
+ @Override
+ public CarouselItem createFromParcel(Parcel in) {
+ return new CarouselItem(in);
+ }
+
+ @Override
+ public CarouselItem[] newArray(int size) {
+ return new CarouselItem[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ TextUtils.writeToParcel(mUpperText, out, flags);
+ out.writeTypedObject(mImage, flags);
+ TextUtils.writeToParcel(mLowerText, out, flags);
+ out.writeTypedObject(mTapAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CarouselItem)) return false;
+ CarouselItem that = (CarouselItem) o;
+ return SmartspaceUtils.isEqual(mUpperText, that.mUpperText) && Objects.equals(
+ mImage,
+ that.mImage) && SmartspaceUtils.isEqual(mLowerText, that.mLowerText)
+ && Objects.equals(mTapAction, that.mTapAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpperText, mImage, mLowerText, mTapAction);
+ }
+
+ @Override
+ public String toString() {
+ return "CarouselItem{"
+ + "mUpperText=" + mUpperText
+ + ", mImage=" + mImage
+ + ", mLowerText=" + mLowerText
+ + ", mTapAction=" + mTapAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link CarouselItem} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private CharSequence mUpperText;
+ private SmartspaceIcon mImage;
+ private CharSequence mLowerText;
+ private SmartspaceTapAction mTapAction;
+
+ /**
+ * Sets the upper text.
+ */
+ @NonNull
+ public Builder setUpperText(@Nullable CharSequence upperText) {
+ mUpperText = upperText;
+ return this;
+ }
+
+ /**
+ * Sets the image.
+ */
+ @NonNull
+ public Builder setImage(@Nullable SmartspaceIcon image) {
+ mImage = image;
+ return this;
+ }
+
+
+ /**
+ * Sets the lower text.
+ */
+ @NonNull
+ public Builder setLowerText(@Nullable CharSequence lowerText) {
+ mLowerText = lowerText;
+ return this;
+ }
+
+ /**
+ * Sets the tap action.
+ */
+ @NonNull
+ public Builder setTapAction(@Nullable SmartspaceTapAction tapAction) {
+ mTapAction = tapAction;
+ return this;
+ }
+
+ /**
+ * Builds a new CarouselItem instance.
+ *
+ * @throws IllegalStateException if all the rendering data is empty.
+ */
+ @NonNull
+ public CarouselItem build() {
+ if (TextUtils.isEmpty(mUpperText) && mImage == null && TextUtils.isEmpty(
+ mLowerText)) {
+ throw new IllegalStateException("Carousel data is empty");
+ }
+ return new CarouselItem(mUpperText, mImage, mLowerText, mTapAction);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java
new file mode 100644
index 0000000..7e2f74e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the combined-card Ui
+ * Template.
+ *
+ * We only support 1 sub-list card combined with 1 carousel card. And we may expand our supported
+ * combinations in the future.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCombinedCardsUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** A list of secondary cards. */
+ @NonNull
+ private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+ SmartspaceCombinedCardsUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mCombinedCardDataList = in.createTypedArrayList(SmartspaceDefaultUiTemplateData.CREATOR);
+ }
+
+ private SmartspaceCombinedCardsUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mCombinedCardDataList = combinedCardDataList;
+ }
+
+ @NonNull
+ public List<SmartspaceDefaultUiTemplateData> getCombinedCardDataList() {
+ return mCombinedCardDataList;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceCombinedCardsUiTemplateData> CREATOR =
+ new Creator<SmartspaceCombinedCardsUiTemplateData>() {
+ @Override
+ public SmartspaceCombinedCardsUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceCombinedCardsUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceCombinedCardsUiTemplateData[] newArray(int size) {
+ return new SmartspaceCombinedCardsUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedList(mCombinedCardDataList);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceCombinedCardsUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceCombinedCardsUiTemplateData that = (SmartspaceCombinedCardsUiTemplateData) o;
+ return mCombinedCardDataList.equals(that.mCombinedCardDataList);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCombinedCardDataList);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceCombinedCardsUiTemplateData{"
+ + "mCombinedCardDataList=" + mCombinedCardDataList
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceCombinedCardsUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+ /**
+ * A builder for {@link SmartspaceCombinedCardsUiTemplateData}.
+ */
+ public Builder(@NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+ super(SmartspaceTarget.UI_TEMPLATE_COMBINED_CARDS);
+ mCombinedCardDataList = Objects.requireNonNull(combinedCardDataList);
+ }
+
+ /**
+ * Builds a new SmartspaceCombinedCardsUiTemplateData instance.
+ *
+ * @throws IllegalStateException if any required non-null field is null
+ */
+ @NonNull
+ public SmartspaceCombinedCardsUiTemplateData build() {
+ if (mCombinedCardDataList == null) {
+ throw new IllegalStateException("Please assign a value to all @NonNull args.");
+ }
+ return new SmartspaceCombinedCardsUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mCombinedCardDataList);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
new file mode 100644
index 0000000..742d5c9
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
@@ -0,0 +1,459 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget.UiTemplateType;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the default Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("ParcelNotFinal")
+public class SmartspaceDefaultUiTemplateData implements Parcelable {
+
+ /**
+ * {@link UiTemplateType} indicating the template type of this template data.
+ *
+ * @see UiTemplateType
+ */
+ @UiTemplateType
+ private final int mTemplateType;
+
+ /**
+ * Title text and title icon are shown at the first row. When both are absent, the date view
+ * will be used, which has its own tap action applied to the title area.
+ */
+ @Nullable
+ private final CharSequence mTitleText;
+
+ @Nullable
+ private final SmartspaceIcon mTitleIcon;
+
+ /** Subtitle text and icon are shown at the second row. */
+ @Nullable
+ private final CharSequence mSubtitleText;
+
+ @Nullable
+ private final SmartspaceIcon mSubTitleIcon;
+
+ /**
+ * Primary tap action for the entire card, including the blank spaces, except: 1. When title is
+ * absent, the date view's default tap action is used; 2. Supplemental subtitle uses its own tap
+ * action if being set; 3. Secondary card uses its own tap action if being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mPrimaryTapAction;
+
+ /**
+ * Supplemental subtitle text and icon are shown at the second row following the subtitle text.
+ * Mainly used for weather info on non-weather card.
+ */
+ @Nullable
+ private final CharSequence mSupplementalSubtitleText;
+
+ @Nullable
+ private final SmartspaceIcon mSupplementalSubtitleIcon;
+
+ /**
+ * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if
+ * not being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mSupplementalSubtitleTapAction;
+
+ /**
+ * Supplemental alarm text is specifically used for holiday alarm, which is appended to "next
+ * alarm".
+ */
+ @Nullable
+ private final CharSequence mSupplementalAlarmText;
+
+ SmartspaceDefaultUiTemplateData(@NonNull Parcel in) {
+ mTemplateType = in.readInt();
+ mTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mPrimaryTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ mSupplementalSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSupplementalSubtitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSupplementalSubtitleTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ mSupplementalAlarmText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Should ONLY used by subclasses. For the general instance creation, please use
+ * SmartspaceDefaultUiTemplateData.Builder.
+ */
+ SmartspaceDefaultUiTemplateData(@UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText) {
+ mTemplateType = templateType;
+ mTitleText = titleText;
+ mTitleIcon = titleIcon;
+ mSubtitleText = subtitleText;
+ mSubTitleIcon = subTitleIcon;
+ mPrimaryTapAction = primaryTapAction;
+ mSupplementalSubtitleText = supplementalSubtitleText;
+ mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+ mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+ mSupplementalAlarmText = supplementalAlarmText;
+ }
+
+ @UiTemplateType
+ public int getTemplateType() {
+ return mTemplateType;
+ }
+
+ @Nullable
+ public CharSequence getTitleText() {
+ return mTitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getTitleIcon() {
+ return mTitleIcon;
+ }
+
+ @Nullable
+ public CharSequence getSubtitleText() {
+ return mSubtitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSubTitleIcon() {
+ return mSubTitleIcon;
+ }
+
+ @Nullable
+ public CharSequence getSupplementalSubtitleText() {
+ return mSupplementalSubtitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSupplementalSubtitleIcon() {
+ return mSupplementalSubtitleIcon;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getPrimaryTapAction() {
+ return mPrimaryTapAction;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSupplementalSubtitleTapAction() {
+ return mSupplementalSubtitleTapAction;
+ }
+
+ @Nullable
+ public CharSequence getSupplementalAlarmText() {
+ return mSupplementalAlarmText;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceDefaultUiTemplateData> CREATOR =
+ new Creator<SmartspaceDefaultUiTemplateData>() {
+ @Override
+ public SmartspaceDefaultUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceDefaultUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceDefaultUiTemplateData[] newArray(int size) {
+ return new SmartspaceDefaultUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mTemplateType);
+ TextUtils.writeToParcel(mTitleText, out, flags);
+ out.writeTypedObject(mTitleIcon, flags);
+ TextUtils.writeToParcel(mSubtitleText, out, flags);
+ out.writeTypedObject(mSubTitleIcon, flags);
+ out.writeTypedObject(mPrimaryTapAction, flags);
+ TextUtils.writeToParcel(mSupplementalSubtitleText, out, flags);
+ out.writeTypedObject(mSupplementalSubtitleIcon, flags);
+ out.writeTypedObject(mSupplementalSubtitleTapAction, flags);
+ TextUtils.writeToParcel(mSupplementalAlarmText, out, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceDefaultUiTemplateData)) return false;
+ SmartspaceDefaultUiTemplateData that = (SmartspaceDefaultUiTemplateData) o;
+ return mTemplateType == that.mTemplateType && SmartspaceUtils.isEqual(mTitleText,
+ that.mTitleText)
+ && Objects.equals(mTitleIcon, that.mTitleIcon)
+ && SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText)
+ && Objects.equals(mSubTitleIcon, that.mSubTitleIcon)
+ && Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction)
+ && SmartspaceUtils.isEqual(mSupplementalSubtitleText,
+ that.mSupplementalSubtitleText)
+ && Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon)
+ && Objects.equals(mSupplementalSubtitleTapAction,
+ that.mSupplementalSubtitleTapAction)
+ && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubTitleIcon,
+ mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon,
+ mSupplementalSubtitleTapAction, mSupplementalAlarmText);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceDefaultUiTemplateData{"
+ + "mTemplateType=" + mTemplateType
+ + ", mTitleText=" + mTitleText
+ + ", mTitleIcon=" + mTitleIcon
+ + ", mSubtitleText=" + mSubtitleText
+ + ", mSubTitleIcon=" + mSubTitleIcon
+ + ", mPrimaryTapAction=" + mPrimaryTapAction
+ + ", mSupplementalSubtitleText=" + mSupplementalSubtitleText
+ + ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon
+ + ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction
+ + ", mSupplementalAlarmText=" + mSupplementalAlarmText
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceDefaultUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("StaticFinalBuilder")
+ public static class Builder {
+ @UiTemplateType
+ private final int mTemplateType;
+ private CharSequence mTitleText;
+ private SmartspaceIcon mTitleIcon;
+ private CharSequence mSubtitleText;
+ private SmartspaceIcon mSubTitleIcon;
+ private SmartspaceTapAction mPrimaryTapAction;
+ private CharSequence mSupplementalSubtitleText;
+ private SmartspaceIcon mSupplementalSubtitleIcon;
+ private SmartspaceTapAction mSupplementalSubtitleTapAction;
+ private CharSequence mSupplementalAlarmText;
+
+ /**
+ * A builder for {@link SmartspaceDefaultUiTemplateData}.
+ *
+ * @param templateType the {@link UiTemplateType} of this template data.
+ */
+ public Builder(@UiTemplateType int templateType) {
+ mTemplateType = templateType;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @UiTemplateType
+ @SuppressLint("GetterOnBuilder")
+ int getTemplateType() {
+ return mTemplateType;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getTitleText() {
+ return mTitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getTitleIcon() {
+ return mTitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSubtitleText() {
+ return mSubtitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getSubTitleIcon() {
+ return mSubTitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceTapAction getPrimaryTapAction() {
+ return mPrimaryTapAction;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSupplementalSubtitleText() {
+ return mSupplementalSubtitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getSupplementalSubtitleIcon() {
+ return mSupplementalSubtitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceTapAction getSupplementalSubtitleTapAction() {
+ return mSupplementalSubtitleTapAction;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSupplementalAlarmText() {
+ return mSupplementalAlarmText;
+ }
+
+ /**
+ * Sets the card title.
+ */
+ @NonNull
+ public Builder setTitleText(@NonNull CharSequence titleText) {
+ mTitleText = titleText;
+ return this;
+ }
+
+ /**
+ * Sets the card title icon.
+ */
+ @NonNull
+ public Builder setTitleIcon(@NonNull SmartspaceIcon titleIcon) {
+ mTitleIcon = titleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card subtitle.
+ */
+ @NonNull
+ public Builder setSubtitleText(@NonNull CharSequence subtitleText) {
+ mSubtitleText = subtitleText;
+ return this;
+ }
+
+ /**
+ * Sets the card subtitle icon.
+ */
+ @NonNull
+ public Builder setSubTitleIcon(@NonNull SmartspaceIcon subTitleIcon) {
+ mSubTitleIcon = subTitleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card primary tap action.
+ */
+ @NonNull
+ public Builder setPrimaryTapAction(@NonNull SmartspaceTapAction primaryTapAction) {
+ mPrimaryTapAction = primaryTapAction;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle text.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleText(@NonNull CharSequence supplementalSubtitleText) {
+ mSupplementalSubtitleText = supplementalSubtitleText;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle icon.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleIcon(
+ @NonNull SmartspaceIcon supplementalSubtitleIcon) {
+ mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not
+ * being
+ * set.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleTapAction(
+ @NonNull SmartspaceTapAction supplementalSubtitleTapAction) {
+ mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental alarm text.
+ */
+ @NonNull
+ public Builder setSupplementalAlarmText(@NonNull CharSequence supplementalAlarmText) {
+ mSupplementalAlarmText = supplementalAlarmText;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceDefaultUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceDefaultUiTemplateData build() {
+ return new SmartspaceDefaultUiTemplateData(mTemplateType, mTitleText, mTitleIcon,
+ mSubtitleText, mSubTitleIcon, mPrimaryTapAction, mSupplementalSubtitleText,
+ mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction,
+ mSupplementalAlarmText);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
new file mode 100644
index 0000000..c76af27
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the head-to-head Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceHeadToHeadUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ @Nullable
+ private final CharSequence mHeadToHeadTitle;
+ @Nullable
+ private final SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+ @Nullable
+ private final SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+ @Nullable
+ private final CharSequence mHeadToHeadFirstCompetitorText;
+ @Nullable
+ private final CharSequence mHeadToHeadSecondCompetitorText;
+
+ /** Tap action for the head-to-head secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mHeadToHeadAction;
+
+ SmartspaceHeadToHeadUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mHeadToHeadTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadFirstCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mHeadToHeadSecondCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mHeadToHeadFirstCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadSecondCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceHeadToHeadUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @Nullable CharSequence headToHeadTitle,
+ @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon,
+ @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon,
+ @Nullable CharSequence headToHeadFirstCompetitorText,
+ @Nullable CharSequence headToHeadSecondCompetitorText,
+ @Nullable SmartspaceTapAction headToHeadAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mHeadToHeadTitle = headToHeadTitle;
+ mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+ mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+ mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+ mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+ mHeadToHeadAction = headToHeadAction;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadTitle() {
+ return mHeadToHeadTitle;
+ }
+
+ @Nullable
+ public SmartspaceIcon getHeadToHeadFirstCompetitorIcon() {
+ return mHeadToHeadFirstCompetitorIcon;
+ }
+
+ @Nullable
+ public SmartspaceIcon getHeadToHeadSecondCompetitorIcon() {
+ return mHeadToHeadSecondCompetitorIcon;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadFirstCompetitorText() {
+ return mHeadToHeadFirstCompetitorText;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadSecondCompetitorText() {
+ return mHeadToHeadSecondCompetitorText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getHeadToHeadAction() {
+ return mHeadToHeadAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceHeadToHeadUiTemplateData> CREATOR =
+ new Creator<SmartspaceHeadToHeadUiTemplateData>() {
+ @Override
+ public SmartspaceHeadToHeadUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceHeadToHeadUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceHeadToHeadUiTemplateData[] newArray(int size) {
+ return new SmartspaceHeadToHeadUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ TextUtils.writeToParcel(mHeadToHeadTitle, out, flags);
+ out.writeTypedObject(mHeadToHeadFirstCompetitorIcon, flags);
+ out.writeTypedObject(mHeadToHeadSecondCompetitorIcon, flags);
+ TextUtils.writeToParcel(mHeadToHeadFirstCompetitorText, out, flags);
+ TextUtils.writeToParcel(mHeadToHeadSecondCompetitorText, out, flags);
+ out.writeTypedObject(mHeadToHeadAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceHeadToHeadUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceHeadToHeadUiTemplateData that = (SmartspaceHeadToHeadUiTemplateData) o;
+ return SmartspaceUtils.isEqual(mHeadToHeadTitle, that.mHeadToHeadTitle) && Objects.equals(
+ mHeadToHeadFirstCompetitorIcon, that.mHeadToHeadFirstCompetitorIcon)
+ && Objects.equals(
+ mHeadToHeadSecondCompetitorIcon, that.mHeadToHeadSecondCompetitorIcon)
+ && SmartspaceUtils.isEqual(mHeadToHeadFirstCompetitorText,
+ that.mHeadToHeadFirstCompetitorText)
+ && SmartspaceUtils.isEqual(mHeadToHeadSecondCompetitorText,
+ that.mHeadToHeadSecondCompetitorText)
+ && Objects.equals(
+ mHeadToHeadAction, that.mHeadToHeadAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mHeadToHeadTitle, mHeadToHeadFirstCompetitorIcon,
+ mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+ mHeadToHeadSecondCompetitorText,
+ mHeadToHeadAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceHeadToHeadUiTemplateData{"
+ + "mH2HTitle=" + mHeadToHeadTitle
+ + ", mH2HFirstCompetitorIcon=" + mHeadToHeadFirstCompetitorIcon
+ + ", mH2HSecondCompetitorIcon=" + mHeadToHeadSecondCompetitorIcon
+ + ", mH2HFirstCompetitorText=" + mHeadToHeadFirstCompetitorText
+ + ", mH2HSecondCompetitorText=" + mHeadToHeadSecondCompetitorText
+ + ", mH2HAction=" + mHeadToHeadAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceHeadToHeadUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private CharSequence mHeadToHeadTitle;
+ private SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+ private SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+ private CharSequence mHeadToHeadFirstCompetitorText;
+ private CharSequence mHeadToHeadSecondCompetitorText;
+ private SmartspaceTapAction mHeadToHeadAction;
+
+ /**
+ * A builder for {@link SmartspaceHeadToHeadUiTemplateData}.
+ */
+ public Builder() {
+ super(SmartspaceTarget.UI_TEMPLATE_HEAD_TO_HEAD);
+ }
+
+ /**
+ * Sets the head-to-head card's title
+ */
+ @NonNull
+ public Builder setHeadToHeadTitle(@Nullable CharSequence headToHeadTitle) {
+ mHeadToHeadTitle = headToHeadTitle;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's first competitor icon
+ */
+ @NonNull
+ public Builder setHeadToHeadFirstCompetitorIcon(
+ @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon) {
+ mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's second competitor icon
+ */
+ @NonNull
+ public Builder setHeadToHeadSecondCompetitorIcon(
+ @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon) {
+ mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's first competitor text
+ */
+ @NonNull
+ public Builder setHeadToHeadFirstCompetitorText(
+ @Nullable CharSequence headToHeadFirstCompetitorText) {
+ mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's second competitor text
+ */
+ @NonNull
+ public Builder setHeadToHeadSecondCompetitorText(
+ @Nullable CharSequence headToHeadSecondCompetitorText) {
+ mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's tap action
+ */
+ @NonNull
+ public Builder setHeadToHeadAction(@Nullable SmartspaceTapAction headToHeadAction) {
+ mHeadToHeadAction = headToHeadAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceHeadToHeadUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceHeadToHeadUiTemplateData build() {
+ return new SmartspaceHeadToHeadUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mHeadToHeadTitle,
+ mHeadToHeadFirstCompetitorIcon,
+ mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+ mHeadToHeadSecondCompetitorText,
+ mHeadToHeadAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
new file mode 100644
index 0000000..70b3095
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
@@ -0,0 +1,148 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceUtils;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds the information for a Smartspace-card icon. Including the icon image itself, and an
+ * optional content description as the icon's accessibility description.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceIcon implements Parcelable {
+
+ @NonNull
+ private final Icon mIcon;
+
+ @Nullable
+ private final CharSequence mContentDescription;
+
+ SmartspaceIcon(@NonNull Parcel in) {
+ mIcon = in.readTypedObject(Icon.CREATOR);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ private SmartspaceIcon(@NonNull Icon icon, @Nullable CharSequence contentDescription) {
+ mIcon = icon;
+ mContentDescription = contentDescription;
+ }
+
+ @NonNull
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ @Nullable
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ @NonNull
+ public static final Creator<SmartspaceIcon> CREATOR = new Creator<SmartspaceIcon>() {
+ @Override
+ public SmartspaceIcon createFromParcel(Parcel in) {
+ return new SmartspaceIcon(in);
+ }
+
+ @Override
+ public SmartspaceIcon[] newArray(int size) {
+ return new SmartspaceIcon[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceIcon)) return false;
+ SmartspaceIcon that = (SmartspaceIcon) o;
+ return mIcon.equals(that.mIcon) && SmartspaceUtils.isEqual(mContentDescription,
+ that.mContentDescription);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIcon, mContentDescription);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeTypedObject(mIcon, flags);
+ TextUtils.writeToParcel(mContentDescription, out, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceIcon{"
+ + "mImage=" + mIcon
+ + ", mContentDescription='" + mContentDescription + '\''
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceIcon} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private Icon mIcon;
+ private CharSequence mContentDescription;
+
+ /**
+ * A builder for {@link SmartspaceIcon}.
+ *
+ * @param icon the icon image of this smartspace icon.
+ */
+ public Builder(@NonNull Icon icon) {
+ mIcon = Objects.requireNonNull(icon);
+ }
+
+ /**
+ * Sets the icon's content description.
+ */
+ @NonNull
+ public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceIcon instance.
+ */
+ @NonNull
+ public SmartspaceIcon build() {
+ return new SmartspaceIcon(mIcon, mContentDescription);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
new file mode 100644
index 0000000..287cf8e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-card Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubCardUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Icon for the sub-card. */
+ @NonNull
+ private final SmartspaceIcon mSubCardIcon;
+
+ /** Text for the sub-card, which shows below the icon when being set. */
+ @Nullable
+ private final CharSequence mSubCardText;
+
+ /** Tap action for the sub-card secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubCardAction;
+
+ SmartspaceSubCardUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubCardIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubCardText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubCardAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubCardUiTemplateData(int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull SmartspaceIcon subCardIcon,
+ @Nullable CharSequence subCardText,
+ @Nullable SmartspaceTapAction subCardAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubCardIcon = subCardIcon;
+ mSubCardText = subCardText;
+ mSubCardAction = subCardAction;
+ }
+
+ @NonNull
+ public SmartspaceIcon getSubCardIcon() {
+ return mSubCardIcon;
+ }
+
+ @Nullable
+ public CharSequence getSubCardText() {
+ return mSubCardText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubCardAction() {
+ return mSubCardAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubCardUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubCardUiTemplateData>() {
+ @Override
+ public SmartspaceSubCardUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubCardUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubCardUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubCardUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedObject(mSubCardIcon, flags);
+ TextUtils.writeToParcel(mSubCardText, out, flags);
+ out.writeTypedObject(mSubCardAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubCardUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubCardUiTemplateData that = (SmartspaceSubCardUiTemplateData) o;
+ return mSubCardIcon.equals(that.mSubCardIcon) && SmartspaceUtils.isEqual(mSubCardText,
+ that.mSubCardText) && Objects.equals(mSubCardAction,
+ that.mSubCardAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubCardIcon, mSubCardText, mSubCardAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubCardUiTemplateData{"
+ + "mSubCardIcon=" + mSubCardIcon
+ + ", mSubCardText=" + mSubCardText
+ + ", mSubCardAction=" + mSubCardAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubCardUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final SmartspaceIcon mSubCardIcon;
+ private CharSequence mSubCardText;
+ private SmartspaceTapAction mSubCardAction;
+
+ /**
+ * A builder for {@link SmartspaceSubCardUiTemplateData}.
+ */
+ public Builder(@NonNull SmartspaceIcon subCardIcon) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_CARD);
+ mSubCardIcon = Objects.requireNonNull(subCardIcon);
+ }
+
+ /**
+ * Sets the card title text.
+ */
+ @NonNull
+ public Builder setSubCardAction(@NonNull CharSequence subCardTitleText) {
+ mSubCardText = subCardTitleText;
+ return this;
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setSubCardAction(@NonNull SmartspaceTapAction subCardAction) {
+ mSubCardAction = subCardAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubCardUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubCardUiTemplateData build() {
+ return new SmartspaceSubCardUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon,
+ mSubCardText,
+ mSubCardAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
new file mode 100644
index 0000000..c479993
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-image Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubImageUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Texts are shown next to the image as a vertical list */
+ @NonNull
+ private final List<CharSequence> mSubImageTexts;
+
+ /** If multiple images are passed in, they will be rendered as GIF. */
+ @NonNull
+ private final List<SmartspaceIcon> mSubImages;
+
+ /** Tap action for the sub-image secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubImageAction;
+
+ SmartspaceSubImageUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubImageTexts = Arrays.asList(in.readCharSequenceArray());
+ mSubImages = in.createTypedArrayList(SmartspaceIcon.CREATOR);
+ mSubImageAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubImageUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<CharSequence> subImageTexts,
+ @NonNull List<SmartspaceIcon> subImages,
+ @Nullable SmartspaceTapAction subImageAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubImageTexts = subImageTexts;
+ mSubImages = subImages;
+ mSubImageAction = subImageAction;
+ }
+
+ @NonNull
+ public List<CharSequence> getSubImageTexts() {
+ return mSubImageTexts;
+ }
+
+ @NonNull
+ public List<SmartspaceIcon> getSubImages() {
+ return mSubImages;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubImageAction() {
+ return mSubImageAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubImageUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubImageUiTemplateData>() {
+ @Override
+ public SmartspaceSubImageUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubImageUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubImageUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubImageUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeCharSequenceList(new ArrayList<>(mSubImageTexts));
+ out.writeTypedList(mSubImages);
+ out.writeTypedObject(mSubImageAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubImageUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubImageUiTemplateData that = (SmartspaceSubImageUiTemplateData) o;
+ return Objects.equals(mSubImageTexts, that.mSubImageTexts)
+ && Objects.equals(mSubImages, that.mSubImages) && Objects.equals(
+ mSubImageAction, that.mSubImageAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubImageTexts, mSubImages, mSubImageAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubImageUiTemplateData{"
+ + "mSubImageTexts=" + mSubImageTexts
+ + ", mSubImages=" + mSubImages
+ + ", mSubImageAction=" + mSubImageAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubImageUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<CharSequence> mSubImageTexts;
+ private final List<SmartspaceIcon> mSubImages;
+ private SmartspaceTapAction mSubImageAction;
+
+ /**
+ * A builder for {@link SmartspaceSubImageUiTemplateData}.
+ */
+ public Builder(@NonNull List<CharSequence> subImageTexts,
+ @NonNull List<SmartspaceIcon> subImages) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_IMAGE);
+ mSubImageTexts = Objects.requireNonNull(subImageTexts);
+ mSubImages = Objects.requireNonNull(subImages);
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction subImageAction) {
+ mSubImageAction = subImageAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubImageUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubImageUiTemplateData build() {
+ return new SmartspaceSubImageUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts,
+ mSubImages,
+ mSubImageAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
new file mode 100644
index 0000000..b5d9645
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-list Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubListUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ @Nullable
+ private final SmartspaceIcon mSubListIcon;
+ @NonNull
+ private final List<CharSequence> mSubListTexts;
+
+ /** Tap action for the sub-list secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubListAction;
+
+ SmartspaceSubListUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubListIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubListTexts = Arrays.asList(in.readCharSequenceArray());
+ mSubListAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubListUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @Nullable SmartspaceIcon subListIcon,
+ @NonNull List<CharSequence> subListTexts,
+ @Nullable SmartspaceTapAction subListAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubListIcon = subListIcon;
+ mSubListTexts = subListTexts;
+ mSubListAction = subListAction;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSubListIcon() {
+ return mSubListIcon;
+ }
+
+ @NonNull
+ public List<CharSequence> getSubListTexts() {
+ return mSubListTexts;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubListAction() {
+ return mSubListAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubListUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubListUiTemplateData>() {
+ @Override
+ public SmartspaceSubListUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubListUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubListUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubListUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedObject(mSubListIcon, flags);
+ out.writeCharSequenceList(new ArrayList<>(mSubListTexts));
+ out.writeTypedObject(mSubListAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubListUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubListUiTemplateData that = (SmartspaceSubListUiTemplateData) o;
+ return Objects.equals(mSubListIcon, that.mSubListIcon) && Objects.equals(
+ mSubListTexts, that.mSubListTexts) && Objects.equals(mSubListAction,
+ that.mSubListAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubListIcon, mSubListTexts, mSubListAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubListUiTemplateData{"
+ + "mSubListIcon=" + mSubListIcon
+ + ", mSubListTexts=" + mSubListTexts
+ + ", mSubListAction=" + mSubListAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubListUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private SmartspaceIcon mSubListIcon;
+ private final List<CharSequence> mSubListTexts;
+ private SmartspaceTapAction mSubListAction;
+
+ /**
+ * A builder for {@link SmartspaceSubListUiTemplateData}.
+ */
+ public Builder(@NonNull List<CharSequence> subListTexts) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_LIST);
+ mSubListTexts = Objects.requireNonNull(subListTexts);
+ }
+
+ /**
+ * Sets the sub-list card icon.
+ */
+ @NonNull
+ public Builder setSubListIcon(@NonNull SmartspaceIcon subListIcon) {
+ mSubListIcon = subListIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction subListAction) {
+ mSubListAction = subListAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubListUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubListUiTemplateData build() {
+ return new SmartspaceSubListUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon,
+ mSubListTexts,
+ mSubListAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
new file mode 100644
index 0000000..27d8e5f
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
@@ -0,0 +1,232 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.app.smartspace.SmartspaceUtils;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTapAction} represents an action which can be taken by a user by tapping on
+ * either the title, the subtitle or on the icon. Supported instances are Intents and
+ * PendingIntents. These actions can be called from another process or within the client process.
+ *
+ * Clients can also receive ShorcutInfos in the extras bundle.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTapAction implements Parcelable {
+
+ /** A unique Id of this {@link SmartspaceTapAction}. */
+ @Nullable
+ private final CharSequence mId;
+
+ @Nullable
+ private final Intent mIntent;
+
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ @Nullable
+ private final UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ SmartspaceTapAction(@NonNull Parcel in) {
+ mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ mExtras = in.readBundle();
+ }
+
+ private SmartspaceTapAction(@Nullable CharSequence id, @Nullable Intent intent,
+ @Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle,
+ @Nullable Bundle extras) {
+ mId = id;
+ mIntent = intent;
+ mPendingIntent = pendingIntent;
+ mUserHandle = userHandle;
+ mExtras = extras;
+ }
+
+ @Nullable
+ public CharSequence getId() {
+ return mId;
+ }
+
+ @SuppressLint("IntentBuilderName")
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Nullable
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ @Nullable
+ @SuppressLint("NullableCollection")
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ TextUtils.writeToParcel(mId, out, flags);
+ out.writeTypedObject(mIntent, flags);
+ out.writeTypedObject(mPendingIntent, flags);
+ out.writeTypedObject(mUserHandle, flags);
+ out.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<SmartspaceTapAction> CREATOR = new Creator<SmartspaceTapAction>() {
+ @Override
+ public SmartspaceTapAction createFromParcel(Parcel in) {
+ return new SmartspaceTapAction(in);
+ }
+
+ @Override
+ public SmartspaceTapAction[] newArray(int size) {
+ return new SmartspaceTapAction[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceTapAction)) return false;
+ SmartspaceTapAction that = (SmartspaceTapAction) o;
+ return SmartspaceUtils.isEqual(mId, that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceTapAction{"
+ + "mId=" + mId
+ + "mIntent=" + mIntent
+ + ", mPendingIntent=" + mPendingIntent
+ + ", mUserHandle=" + mUserHandle
+ + ", mExtras=" + mExtras
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceTapAction} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private CharSequence mId;
+ private Intent mIntent;
+ private PendingIntent mPendingIntent;
+ private UserHandle mUserHandle;
+ private Bundle mExtras;
+
+ /**
+ * A builder for {@link SmartspaceTapAction}.
+ *
+ * @param id A unique Id of this {@link SmartspaceTapAction}.
+ */
+ public Builder(@NonNull CharSequence id) {
+ mId = Objects.requireNonNull(id);
+ }
+
+ /**
+ * Sets the action intent.
+ */
+ @NonNull
+ public Builder setIntent(@NonNull Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the pending intent.
+ */
+ @NonNull
+ public Builder setPendingIntent(@NonNull PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the user handle.
+ */
+ @NonNull
+ @SuppressLint("UserHandleName")
+ public Builder setUserHandle(@Nullable UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ /**
+ * Sets the extras.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceTapAction instance.
+ *
+ * @throws IllegalStateException if the tap action is empty.
+ */
+ @NonNull
+ public SmartspaceTapAction build() {
+ if (mIntent == null && mPendingIntent == null && mExtras == null) {
+ throw new IllegalStateException("Please assign at least 1 valid tap field");
+ }
+ return new SmartspaceTapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras);
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/app/usage/BroadcastResponseStats.aidl
similarity index 89%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/app/usage/BroadcastResponseStats.aidl
index 861a4ed..5992841 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/app/usage/BroadcastResponseStats.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.app.usage;
-parcelable DeviceInfo;
+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/app/wallpapereffectsgeneration/CameraAttributes.java b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
new file mode 100644
index 0000000..dfbc7a4
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wallpapereffectsgeneration;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Representing the position and other parameters of camera of a single frame.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CameraAttributes implements Parcelable {
+ /**
+ * The location of the anchor within the 3D scene.
+ * Expecting 3 floats representing the x, y, z coordinates
+ * of the anchor point.
+ */
+ @NonNull
+ private float[] mAnchorPointInWorldSpace;
+ /**
+ * Where the anchor point should project to in the output image.
+ * Expecting 2 floats representing the u,v coordinates of the
+ * anchor point.
+ */
+ @NonNull
+ private float[] mAnchorPointInOutputUvSpace;
+ /**
+ * Specifies the amount of yaw orbit rotation the camera should perform
+ * around the anchor point in world space.
+ */
+ private float mCameraOrbitYawDegrees;
+ /**
+ * Specifies the amount of pitch orbit rotation the camera should perform
+ * around the anchor point in world space.
+ */
+ private float mCameraOrbitPitchDegrees;
+ /**
+ * Specifies by how much the camera should be placed towards the anchor
+ * point in world space, which is also called dolly distance.
+ */
+ private float mDollyDistanceInWorldSpace;
+ /**
+ * Specifies the vertical fov degrees of the virtual image.
+ */
+ private float mVerticalFovDegrees;
+ /**
+ * The frustum of near plane.
+ */
+ private float mFrustumNearInWorldSpace;
+ /**
+ * The frustum of far plane.
+ */
+ private float mFrustumFarInWorldSpace;
+
+ private CameraAttributes(Parcel in) {
+ this.mCameraOrbitYawDegrees = in.readFloat();
+ this.mCameraOrbitPitchDegrees = in.readFloat();
+ this.mDollyDistanceInWorldSpace = in.readFloat();
+ this.mVerticalFovDegrees = in.readFloat();
+ this.mFrustumNearInWorldSpace = in.readFloat();
+ this.mFrustumFarInWorldSpace = in.readFloat();
+ this.mAnchorPointInWorldSpace = in.createFloatArray();
+ this.mAnchorPointInOutputUvSpace = in.createFloatArray();
+ }
+
+ private CameraAttributes(float[] anchorPointInWorldSpace, float[] anchorPointInOutputUvSpace,
+ float cameraOrbitYawDegrees, float cameraOrbitPitchDegrees,
+ float dollyDistanceInWorldSpace,
+ float verticalFovDegrees, float frustumNearInWorldSpace, float frustumFarInWorldSpace) {
+ mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+ mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+ mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+ mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+ mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+ mVerticalFovDegrees = verticalFovDegrees;
+ mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+ mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+ }
+
+ /**
+ * Get the location of the anchor within the 3D scene. The response float array contains
+ * 3 floats representing the x, y, z coordinates
+ */
+ @NonNull
+ public float[] getAnchorPointInWorldSpace() {
+ return mAnchorPointInWorldSpace;
+ }
+
+ /**
+ * Get where the anchor point should project to in the output image. The response float
+ * array contains 2 floats representing the u,v coordinates of the anchor point.
+ */
+ @NonNull
+ public float[] getAnchorPointInOutputUvSpace() {
+ return mAnchorPointInOutputUvSpace;
+ }
+
+ /**
+ * Get the camera yaw orbit rotation.
+ */
+ public float getCameraOrbitYawDegrees() {
+ return mCameraOrbitYawDegrees;
+ }
+
+ /**
+ * Get the camera pitch orbit rotation.
+ */
+ public float getCameraOrbitPitchDegrees() {
+ return mCameraOrbitPitchDegrees;
+ }
+
+ /**
+ * Get how many units the camera should be placed towards the anchor point in world space.
+ */
+ public float getDollyDistanceInWorldSpace() {
+ return mDollyDistanceInWorldSpace;
+ }
+
+ /**
+ * Get the camera vertical fov degrees.
+ */
+ public float getVerticalFovDegrees() {
+ return mVerticalFovDegrees;
+ }
+
+ /**
+ * Get the frustum in near plane.
+ */
+ public float getFrustumNearInWorldSpace() {
+ return mFrustumNearInWorldSpace;
+ }
+
+ /**
+ * Get the frustum in far plane.
+ */
+ public float getFrustumFarInWorldSpace() {
+ return mFrustumFarInWorldSpace;
+ }
+
+ @NonNull
+ public static final Creator<CameraAttributes> CREATOR = new Creator<CameraAttributes>() {
+ @Override
+ public CameraAttributes createFromParcel(Parcel in) {
+ return new CameraAttributes(in);
+ }
+
+ @Override
+ public CameraAttributes[] newArray(int size) {
+ return new CameraAttributes[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mCameraOrbitYawDegrees);
+ out.writeFloat(mCameraOrbitPitchDegrees);
+ out.writeFloat(mDollyDistanceInWorldSpace);
+ out.writeFloat(mVerticalFovDegrees);
+ out.writeFloat(mFrustumNearInWorldSpace);
+ out.writeFloat(mFrustumFarInWorldSpace);
+ out.writeFloatArray(mAnchorPointInWorldSpace);
+ out.writeFloatArray(mAnchorPointInOutputUvSpace);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Builder for {@link CameraAttributes}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private float[] mAnchorPointInWorldSpace;
+ @NonNull
+ private float[] mAnchorPointInOutputUvSpace;
+ private float mCameraOrbitYawDegrees;
+ private float mCameraOrbitPitchDegrees;
+ private float mDollyDistanceInWorldSpace;
+ private float mVerticalFovDegrees;
+ private float mFrustumNearInWorldSpace;
+ private float mFrustumFarInWorldSpace;
+
+ /**
+ * Constructor with anchor point in world space and anchor point in output image
+ * space.
+ *
+ * @param anchorPointInWorldSpace the location of the anchor within the 3D scene. The
+ * float array contains 3 floats representing the x, y, z coordinates.
+ * @param anchorPointInOutputUvSpace where the anchor point should project to in the
+ * output image. The float array contains 2 floats representing the u,v coordinates
+ * of the anchor point.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull float[] anchorPointInWorldSpace,
+ @NonNull float[] anchorPointInOutputUvSpace) {
+ mAnchorPointInWorldSpace = anchorPointInWorldSpace;
+ mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
+ }
+
+ /**
+ * Sets the camera orbit yaw rotation.
+ */
+ @NonNull
+ public Builder setCameraOrbitYawDegrees(
+ @FloatRange(from = -180.0f, to = 180.0f) float cameraOrbitYawDegrees) {
+ mCameraOrbitYawDegrees = cameraOrbitYawDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the camera orbit pitch rotation.
+ */
+ @NonNull
+ public Builder setCameraOrbitPitchDegrees(
+ @FloatRange(from = -90.0f, to = 90.0f) float cameraOrbitPitchDegrees) {
+ mCameraOrbitPitchDegrees = cameraOrbitPitchDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the camera dolly distance.
+ */
+ @NonNull
+ public Builder setDollyDistanceInWorldSpace(float dollyDistanceInWorldSpace) {
+ mDollyDistanceInWorldSpace = dollyDistanceInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Sets the camera vertical fov degree.
+ */
+ @NonNull
+ public Builder setVerticalFovDegrees(
+ @FloatRange(from = 0.0f, to = 180.0f, fromInclusive = false)
+ float verticalFovDegrees) {
+ mVerticalFovDegrees = verticalFovDegrees;
+ return this;
+ }
+
+ /**
+ * Sets the frustum in near plane.
+ */
+ @NonNull
+ public Builder setFrustumNearInWorldSpace(
+ @FloatRange(from = 0.0f) float frustumNearInWorldSpace) {
+ mFrustumNearInWorldSpace = frustumNearInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Sets the frustum in far plane.
+ */
+ @NonNull
+ public Builder setFrustumFarInWorldSpace(
+ @FloatRange(from = 0.0f) float frustumFarInWorldSpace) {
+ mFrustumFarInWorldSpace = frustumFarInWorldSpace;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link CameraAttributes} instance.
+ */
+ @NonNull
+ public CameraAttributes build() {
+ return new CameraAttributes(mAnchorPointInWorldSpace,
+ mAnchorPointInOutputUvSpace,
+ mCameraOrbitYawDegrees,
+ mCameraOrbitPitchDegrees,
+ mDollyDistanceInWorldSpace,
+ mVerticalFovDegrees,
+ mFrustumNearInWorldSpace,
+ mFrustumFarInWorldSpace);
+ }
+ }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
similarity index 79%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.aidl
index cb602d79..2347746 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.app.wallpapereffectsgeneration;
-parcelable NetworkStateSnapshot;
+parcelable CinematicEffectRequest;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
new file mode 100644
index 0000000..f2e3313
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectRequest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectRequest} is the data class having all the information
+ * passed to wallpaper effects generation service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectRequest implements Parcelable {
+ /**
+ * Unique id of a cienmatic effect generation task.
+ */
+ @NonNull
+ private String mTaskId;
+
+ /**
+ * The bitmap to generate cinematic effect from.
+ */
+ @NonNull
+ private Bitmap mBitmap;
+
+ private CinematicEffectRequest(Parcel in) {
+ this.mTaskId = in.readString();
+ this.mBitmap = Bitmap.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Constructor with task id and bitmap.
+ */
+ public CinematicEffectRequest(@NonNull String taskId, @NonNull Bitmap bitmap) {
+ mTaskId = taskId;
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Returns the task id.
+ */
+ @NonNull
+ public String getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Returns the bitmap of this request.
+ */
+ @NonNull
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CinematicEffectRequest that = (CinematicEffectRequest) o;
+ return mTaskId.equals(that.mTaskId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mTaskId);
+ mBitmap.writeToParcel(out, flags);
+ }
+
+ @NonNull
+ public static final Creator<CinematicEffectRequest> CREATOR =
+ new Creator<CinematicEffectRequest>() {
+ @Override
+ public CinematicEffectRequest createFromParcel(Parcel in) {
+ return new CinematicEffectRequest(in);
+ }
+
+ @Override
+ public CinematicEffectRequest[] newArray(int size) {
+ return new CinematicEffectRequest[size];
+ }
+ };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
similarity index 79%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.aidl
index cb602d79..1bd1e1e 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+ package android.app.wallpapereffectsgeneration;
-parcelable NetworkStateSnapshot;
+ parcelable CinematicEffectResponse;
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
new file mode 100644
index 0000000..1254794
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link CinematicEffectResponse} include textured meshes
+ * and camera attributes of key frames.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CinematicEffectResponse implements Parcelable {
+ /** @hide */
+ @IntDef(prefix = {"CINEMATIC_EFFECT_STATUS_"},
+ value = {CINEMATIC_EFFECT_STATUS_UNKNOWN,
+ CINEMATIC_EFFECT_STATUS_OK,
+ CINEMATIC_EFFECT_STATUS_ERROR,
+ CINEMATIC_EFFECT_STATUS_NOT_READY,
+ CINEMATIC_EFFECT_STATUS_PENDING,
+ CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CinematicEffectStatusCode {}
+
+ /** Cinematic effect generation unknown status. */
+ public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0;
+ /** Cinematic effect generation success. */
+ public static final int CINEMATIC_EFFECT_STATUS_OK = 1;
+ /** Cinematic effect generation failure. */
+ public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2;
+ /** Service not ready for cinematic effect generation. */
+ public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3;
+ /** Cienmatic effect generation process is pending. */
+ public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4;
+ /** Too manay requests for server to handle. */
+ public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5;
+
+ /** @hide */
+ @IntDef(prefix = {"IMAGE_CONTENT_TYPE_"},
+ value = {IMAGE_CONTENT_TYPE_UNKNOWN,
+ IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT,
+ IMAGE_CONTENT_TYPE_LANDSCAPE,
+ IMAGE_CONTENT_TYPE_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ImageContentType {}
+
+ /** Image content unknown. */
+ public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0;
+ /** Image content is people portrait. */
+ public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1;
+ /** Image content is landscape. */
+ public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2;
+ /** Image content is doesn't belong to other types. */
+ public static final int IMAGE_CONTENT_TYPE_OTHER = 3;
+
+
+ @CinematicEffectStatusCode
+ private int mStatusCode;
+
+ /** The id of the cinematic effect generation task. */
+ @NonNull
+ private String mTaskId;
+
+ @ImageContentType
+ private int mImageContentType;
+
+ /** The textured mesh required to render cinematic effect. */
+ @NonNull
+ private List<TexturedMesh> mTexturedMeshes;
+
+ /** The start camera position for animation. */
+ @Nullable
+ private CameraAttributes mStartKeyFrame;
+
+ /** The end camera position for animation. */
+ @Nullable
+ private CameraAttributes mEndKeyFrame;
+
+ private CinematicEffectResponse(Parcel in) {
+ mStatusCode = in.readInt();
+ mTaskId = in.readString();
+ mImageContentType = in.readInt();
+ mTexturedMeshes = new ArrayList<TexturedMesh>();
+ in.readTypedList(mTexturedMeshes, TexturedMesh.CREATOR);
+ mStartKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+ mEndKeyFrame = in.readTypedObject(CameraAttributes.CREATOR);
+ }
+
+ private CinematicEffectResponse(@CinematicEffectStatusCode int statusCode,
+ String taskId,
+ @ImageContentType int imageContentType,
+ List<TexturedMesh> texturedMeshes,
+ CameraAttributes startKeyFrame,
+ CameraAttributes endKeyFrame) {
+ mStatusCode = statusCode;
+ mTaskId = taskId;
+ mImageContentType = imageContentType;
+ mStartKeyFrame = startKeyFrame;
+ mEndKeyFrame = endKeyFrame;
+ mTexturedMeshes = texturedMeshes;
+ }
+
+ /** Gets the cinematic effect generation status code. */
+ @CinematicEffectStatusCode
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+
+ /** Get the task id. */
+ @NonNull
+ public String getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Get the image content type, which briefly classifies what's
+ * the content of image, like people portrait, landscape etc.
+ */
+ @ImageContentType
+ public int getImageContentType() {
+ return mImageContentType;
+ }
+
+ /** Get the textured meshes. */
+ @NonNull
+ public List<TexturedMesh> getTexturedMeshes() {
+ return mTexturedMeshes;
+ }
+
+ /**
+ * Get the camera attributes (position info and other parameters, see docs of
+ * {@link CameraAttributes}) of the start key frame on the animation path.
+ */
+ @Nullable
+ public CameraAttributes getStartKeyFrame() {
+ return mStartKeyFrame;
+ }
+
+ /**
+ * Get the camera attributes (position info and other parameters, see docs of
+ * {@link CameraAttributes}) of the end key frame on the animation path.
+ */
+ @Nullable
+ public CameraAttributes getEndKeyFrame() {
+ return mEndKeyFrame;
+ }
+
+ @NonNull
+ public static final Creator<CinematicEffectResponse> CREATOR =
+ new Creator<CinematicEffectResponse>() {
+ @Override
+ public CinematicEffectResponse createFromParcel(Parcel in) {
+ return new CinematicEffectResponse(in);
+ }
+
+ @Override
+ public CinematicEffectResponse[] newArray(int size) {
+ return new CinematicEffectResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mStatusCode);
+ out.writeString(mTaskId);
+ out.writeInt(mImageContentType);
+ out.writeTypedList(mTexturedMeshes, flags);
+ out.writeTypedObject(mStartKeyFrame, flags);
+ out.writeTypedObject(mEndKeyFrame, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CinematicEffectResponse that = (CinematicEffectResponse) o;
+ return mTaskId.equals(that.mTaskId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTaskId);
+ }
+ /**
+ * Builder of {@link CinematicEffectResponse}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @CinematicEffectStatusCode
+ private int mStatusCode;
+ @NonNull
+ private String mTaskId;
+ @ImageContentType
+ private int mImageContentType;
+ @NonNull
+ private List<TexturedMesh> mTexturedMeshes;
+ @Nullable
+ private CameraAttributes mStartKeyFrame;
+ @Nullable
+ private CameraAttributes mEndKeyFrame;
+
+ /**
+ * Constructor with task id and status code.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@CinematicEffectStatusCode int statusCode, @NonNull String taskId) {
+ mStatusCode = statusCode;
+ mTaskId = taskId;
+ }
+
+ /**
+ * Sets the image content type.
+ */
+ @NonNull
+ public Builder setImageContentType(@ImageContentType int imageContentType) {
+ mImageContentType = imageContentType;
+ return this;
+ }
+
+
+ /**
+ * Sets the textured meshes.
+ */
+ @NonNull
+ public Builder setTexturedMeshes(@NonNull List<TexturedMesh> texturedMeshes) {
+ mTexturedMeshes = texturedMeshes;
+ return this;
+ }
+
+ /**
+ * Sets start key frame.
+ */
+ @NonNull
+ public Builder setStartKeyFrame(@Nullable CameraAttributes startKeyFrame) {
+ mStartKeyFrame = startKeyFrame;
+ return this;
+ }
+
+ /**
+ * Sets end key frame.
+ */
+ @NonNull
+ public Builder setEndKeyFrame(@Nullable CameraAttributes endKeyFrame) {
+ mEndKeyFrame = endKeyFrame;
+ return this;
+ }
+
+ /**
+ * Builds a {@link CinematicEffectResponse} instance.
+ */
+ @NonNull
+ public CinematicEffectResponse build() {
+ if (mTexturedMeshes == null) {
+ // Place holder because build doesn't allow list to be nullable.
+ mTexturedMeshes = new ArrayList<>(0);
+ }
+ return new CinematicEffectResponse(mStatusCode, mTaskId, mImageContentType,
+ mTexturedMeshes, mStartKeyFrame, mEndKeyFrame);
+ }
+ }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.aidl
new file mode 100644
index 0000000..c1a698d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/ICinematicEffectListener.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 android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+
+
+/**
+ * Callback used by system server to notify invoker of {@link WallpaperEffectsGenerationMAnager}
+ * of the cinematic effect generation result.
+ *
+ * @hide
+ */
+oneway interface ICinematicEffectListener {
+ void onCinematicEffectGenerated(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
new file mode 100644
index 0000000..706a89c
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/IWallpaperEffectsGenerationManager.aidl
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.wallpapereffectsgeneration;
+
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+
+/**
+ * Used by {@link android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager}
+ * to to generate effects.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationManager {
+ void generateCinematicEffect(in CinematicEffectRequest request,
+ in ICinematicEffectListener listener);
+
+ void returnCinematicEffectResponse(in CinematicEffectResponse response);
+}
\ No newline at end of file
diff --git a/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
new file mode 100644
index 0000000..630de45
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/TexturedMesh.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wallpapereffectsgeneration;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The textured mesh representation, including a texture (bitmap) to sample from when rendering,
+ * and a mesh consisting of primitives such as triangles. The mesh is represented by an indices
+ * array describing the set of primitives in the mesh, and a vertices array that the indices
+ * refer to.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TexturedMesh implements Parcelable {
+ /**
+ * The texture to sample from when rendering mesh.
+ */
+ @NonNull
+ private Bitmap mBitmap;
+
+ /**
+ * The set of primitives as pointers into the vertices.
+ */
+ @NonNull
+ private int[] mIndices;
+
+ /**
+ * The specific vertices that the indices refer to.
+ */
+ @NonNull
+ private float[] mVertices;
+
+ /** @hide */
+ @IntDef(prefix = {"INDICES_LAYOUT_"}, value = {
+ INDICES_LAYOUT_UNDEFINED,
+ INDICES_LAYOUT_TRIANGLES})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndicesLayoutType {
+ }
+
+ /** Undefined indices layout */
+ public static final int INDICES_LAYOUT_UNDEFINED = 0;
+ /**
+ * Indices layout is triangle. Vertices are grouped into 3 and each
+ * group forms a triangle.
+ */
+ public static final int INDICES_LAYOUT_TRIANGLES = 1;
+
+ @IndicesLayoutType
+ private int mIndicesLayoutType;
+
+ /** @hide */
+ @IntDef(prefix = {"VERTICES_LAYOUT_"}, value = {
+ VERTICES_LAYOUT_UNDEFINED,
+ VERTICES_LAYOUT_POSITION3_UV2})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerticesLayoutType {
+ }
+
+ /**
+ * Undefined vertices layout.
+ */
+ public static final int VERTICES_LAYOUT_UNDEFINED = 0;
+ /**
+ * The vertices array uses 5 numbers to represent a point, in the format
+ * of [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ */
+ public static final int VERTICES_LAYOUT_POSITION3_UV2 = 1;
+
+ @VerticesLayoutType
+ private int mVerticesLayoutType;
+
+ private TexturedMesh(Parcel in) {
+ this.mIndicesLayoutType = in.readInt();
+ this.mVerticesLayoutType = in.readInt();
+ this.mBitmap = in.readTypedObject(Bitmap.CREATOR);
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] bytes = in.readBlob();
+ data.unmarshall(bytes, 0, bytes.length);
+ data.setDataPosition(0);
+ this.mIndices = data.createIntArray();
+ this.mVertices = data.createFloatArray();
+ } finally {
+ data.recycle();
+ }
+ }
+
+ private TexturedMesh(@NonNull Bitmap bitmap, @NonNull int[] indices,
+ @NonNull float[] vertices, @IndicesLayoutType int indicesLayoutType,
+ @VerticesLayoutType int verticesLayoutType) {
+ mBitmap = bitmap;
+ mIndices = indices;
+ mVertices = vertices;
+ mIndicesLayoutType = indicesLayoutType;
+ mVerticesLayoutType = verticesLayoutType;
+ }
+
+ /** Get the bitmap, which is the texture to sample from when rendering. */
+ @NonNull
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ /**
+ * Get the indices as pointers to the vertices array. Depending on the getIndicesLayoutType(),
+ * the primitives may have different shapes. For example, with INDICES_LAYOUT_TRIANGLES,
+ * indices 0, 1, 2 forms a triangle, indices 3, 4, 5 form another triangle.
+ */
+ @NonNull
+ public int[] getIndices() {
+ return mIndices;
+ }
+
+ /**
+ * Get the vertices that the index array refers to. Depending on the getVerticesLayoutType()
+ * result, the vertices array can represent different per-vertex coordinates. For example,
+ * with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+ * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ */
+ @NonNull
+ public float[] getVertices() {
+ return mVertices;
+ }
+
+ /** Get the indices layout type. */
+ @IndicesLayoutType
+ @NonNull
+ public int getIndicesLayoutType() {
+ return mIndicesLayoutType;
+ }
+
+ /** Get the indices layout type. */
+ @VerticesLayoutType
+ @NonNull
+ public int getVerticesLayoutType() {
+ return mVerticesLayoutType;
+ }
+
+ @NonNull
+ public static final Creator<TexturedMesh> CREATOR = new Creator<TexturedMesh>() {
+ @Override
+ public TexturedMesh createFromParcel(Parcel in) {
+ return new TexturedMesh(in);
+ }
+
+ @Override
+ public TexturedMesh[] newArray(int size) {
+ return new TexturedMesh[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mIndicesLayoutType);
+ out.writeInt(mVerticesLayoutType);
+ out.writeTypedObject(mBitmap, flags);
+
+ // Indices and vertices can reach 5MB. Write the data as a Blob,
+ // which will be written to ashmem if too large.
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeIntArray(mIndices);
+ data.writeFloatArray(mVertices);
+ out.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * A builder for {@link TexturedMesh}
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private Bitmap mBitmap;
+ private int[] mIndices;
+ private float[] mVertices;
+ @IndicesLayoutType
+ private int mIndicesLayoutType;
+ @VerticesLayoutType
+ private int mVerticesLayouttype;
+
+ /**
+ * Constructor with bitmap.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull Bitmap bitmap) {
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Set the required indices. The indices should represent the primitives. For example,
+ * with INDICES_LAYOUT_TRIANGLES, indices 0, 1, 2 forms a triangle, indices 3, 4, 5
+ * form another triangle.
+ */
+ @NonNull
+ public Builder setIndices(@NonNull int[] indices) {
+ mIndices = indices;
+ return this;
+ }
+
+ /**
+ * Set the required vertices. The vertices array should represent per-vertex coordinates.
+ * For example, with VERTICES_LAYOUT_POSITION3_UV2 type, vertices are in the format of
+ * [x1, y1, z1, u1, v1, x2, y2, z2, u2, v2, ...].
+ *
+ */
+ @NonNull
+ public Builder setVertices(@NonNull float[] vertices) {
+ mVertices = vertices;
+ return this;
+ }
+
+ /**
+ * Set the required indices layout type.
+ */
+ @NonNull
+ public Builder setIndicesLayoutType(@IndicesLayoutType int indicesLayoutType) {
+ mIndicesLayoutType = indicesLayoutType;
+ return this;
+ }
+
+ /**
+ * Set the required vertices layout type.
+ */
+ @NonNull
+ public Builder setVerticesLayoutType(@VerticesLayoutType int verticesLayoutype) {
+ mVerticesLayouttype = verticesLayoutype;
+ return this;
+ }
+
+ /** Builds a TexturedMesh based on the given parameters. */
+ @NonNull
+ public TexturedMesh build() {
+ return new TexturedMesh(mBitmap, mIndices, mVertices, mIndicesLayoutType,
+ mVerticesLayouttype);
+ }
+ }
+}
diff --git a/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
new file mode 100644
index 0000000..5a1d27d
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/WallpaperEffectsGenerationManager.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wallpapereffectsgeneration;
+
+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 WallpaperEffectsGenerationManager} is the class that passes wallpaper effects
+ * generation requests to wallpaper effect generation service. For example, create a cinematic
+ * and render a cinematic live wallpaper with the response.
+ *
+ * Usage:
+ * <pre>{@code
+ * mWallpaperEffectsGenerationManager =
+ * context.getSystemService(WallpaperEffectsGenerationManager.class);
+ * mWallpaperEffectsGenerationManager.
+ * generateCinematicEffect(cinematicEffectRequest, response->{
+ * // proceed cinematic effect response.
+ * });
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE)
+public final class WallpaperEffectsGenerationManager {
+ /**
+ * Interface for the cinematic effect listener.
+ */
+ public interface CinematicEffectListener {
+ /**
+ * Async call when the cinematic effect response is generated.
+ * Client needs to check the status code of {@link CinematicEffectResponse}
+ * to determine if the effect generation is successful.
+ *
+ * @param response The generated cinematic effect response.
+ */
+ void onCinematicEffectGenerated(@NonNull CinematicEffectResponse response);
+ }
+
+ private final IWallpaperEffectsGenerationManager mService;
+
+ /** @hide */
+ public WallpaperEffectsGenerationManager(
+ @NonNull IWallpaperEffectsGenerationManager service) {
+ mService = service;
+ }
+
+ /**
+ * Execute a {@link android.app.wallpapereffectsgeneration.CinematicEffectRequest} from
+ * the given parameters to the wallpaper effects generation service. After the cinematic
+ * effect response is ready, the given listener is invoked by the system with the response.
+ * The listener may never receive a callback if unexpected error happened when proceeding
+ * request.
+ *
+ * @param request request to generate cinematic effect.
+ * @param executor where the listener is invoked.
+ * @param listener listener invoked when the cinematic effect response is available.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION)
+ public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull CinematicEffectListener listener) {
+ try {
+ mService.generateCinematicEffect(request,
+ new CinematicEffectListenerWrapper(listener, executor));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static final class CinematicEffectListenerWrapper
+ extends ICinematicEffectListener.Stub {
+ @NonNull
+ private final CinematicEffectListener mListener;
+ @NonNull
+ private final Executor mExecutor;
+
+ CinematicEffectListenerWrapper(@NonNull CinematicEffectListener listener,
+ @NonNull Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCinematicEffectGenerated(CinematicEffectResponse response) {
+ mExecutor.execute(() -> mListener.onCinematicEffectGenerated(response));
+ }
+ }
+}
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index a36da88..6e3bbcc 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,7 +24,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* This class provides an API surface for system apps to manipulate the app hibernation
@@ -129,4 +132,38 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the stats from app hibernation for each package provided.
+ *
+ * @param packageNames the set of packages to return stats for
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull Map<String, HibernationStats> getHibernationStatsForUser(
+ @NonNull Set<String> packageNames) {
+ try {
+ return mIAppHibernationService.getHibernationStatsForUser(
+ new ArrayList(packageNames), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the stats from app hibernation for all packages for the user
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull Map<String, HibernationStats> getHibernationStatsForUser() {
+ try {
+ return mIAppHibernationService.getHibernationStatsForUser(
+ null /* packageNames */, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/android/apphibernation/HibernationStats.aidl
similarity index 82%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/android/apphibernation/HibernationStats.aidl
index cb602d79..a92b903 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/android/apphibernation/HibernationStats.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.apphibernation;
-parcelable NetworkStateSnapshot;
+parcelable HibernationStats;
\ No newline at end of file
diff --git a/core/java/android/apphibernation/HibernationStats.java b/core/java/android/apphibernation/HibernationStats.java
new file mode 100644
index 0000000..2c4db82
--- /dev/null
+++ b/core/java/android/apphibernation/HibernationStats.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stats for a hibernating package.
+ * @hide
+ */
+@SystemApi
+public final class HibernationStats implements Parcelable {
+ private final long mDiskBytesSaved;
+
+ /** @hide */
+ public HibernationStats(long diskBytesSaved) {
+ mDiskBytesSaved = diskBytesSaved;
+ }
+
+ private HibernationStats(@NonNull Parcel in) {
+ mDiskBytesSaved = in.readLong();
+ }
+
+ /**
+ * Get the disk storage saved from hibernation in bytes.
+ */
+ public long getDiskBytesSaved() {
+ return mDiskBytesSaved;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mDiskBytesSaved);
+ }
+
+ public static final @NonNull Creator<HibernationStats> CREATOR =
+ new Creator<HibernationStats>() {
+ @Override
+ public HibernationStats createFromParcel(Parcel in) {
+ return new HibernationStats(in);
+ }
+
+ @Override
+ public HibernationStats[] newArray(int size) {
+ return new HibernationStats[size];
+ }
+ };
+}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index afdb3fe..11bb6b5 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -16,6 +16,8 @@
package android.apphibernation;
+import android.apphibernation.HibernationStats;
+
/**
* Binder interface to communicate with AppHibernationService.
* @hide
@@ -26,4 +28,6 @@
boolean isHibernatingGlobally(String packageName);
void setHibernatingGlobally(String packageName, boolean isHibernating);
List<String> getHibernatingPackagesForUser(int userId);
+ Map<String, HibernationStats> getHibernationStatsForUser(in List<String> packageNames,
+ int userId);
}
\ No newline at end of file
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 941e9e2e..4e00da1 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -46,6 +46,25 @@
*/
public abstract void cancelAttentionCheck(AttentionCallbackInternal callback);
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is unregistered. Currently, AttentionManagerService only
+ * anticipates one client and updates one client at a time. If a new client wants to
+ * onboard to receiving Proximity updates, please make a feature request to make proximity
+ * feature multi-client before depending on this feature.
+ *
+ * @param callback a callback that receives the proximity updates
+ * @return {@code true} if the registration should succeed.
+ */
+ public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback);
+
+ /**
+ * Requests to stop providing continuous updates until the callback is registered.
+ *
+ * @param callback a callback that was used in {@link #onStartProximityUpdates}
+ */
+ public abstract void onStopProximityUpdates(ProximityCallbackInternal callback);
+
/** Internal interface for attention callback. */
public abstract static class AttentionCallbackInternal {
/**
@@ -64,4 +83,13 @@
*/
public abstract void onFailure(int error);
}
+
+ /** Internal interface for proximity callback. */
+ public abstract static class ProximityCallbackInternal {
+ /**
+ * @param distance the estimated distance of the user (in meter)
+ * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+ */
+ public abstract void onProximityUpdate(double distance);
+ }
}
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..36802ea 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/companion/TEST_MAPPING b/core/java/android/companion/TEST_MAPPING
index 63f54fa..b561c29 100644
--- a/core/java/android/companion/TEST_MAPPING
+++ b/core/java/android/companion/TEST_MAPPING
@@ -1,12 +1,7 @@
{
- "presubmit": [
+ "imports": [
{
- "name": "CtsOsTestCases",
- "options": [
- {
- "include-filter": "android.os.cts.CompanionDeviceManagerTest"
- }
- ]
+ "path": "frameworks/base/services/companion"
}
]
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2ddfeb4..1d0f7c0 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.os.Parcel;
@@ -106,11 +107,13 @@
}
/**
- * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+ * Returns the set of activities allowed to be streamed, or {@code null} if all activities are
+ * allowed, except the ones explicitly blocked.
*
* @see Builder#setAllowedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getAllowedActivities() {
if (mAllowedActivities == null) {
@@ -120,12 +123,13 @@
}
/**
- * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
- * set.
+ * Returns the set of activities that are blocked from streaming, or {@code null} to indicate
+ * that all activities in {@link #getAllowedActivities} are allowed.
*
* @see Builder#setBlockedActivities(Set)
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
@Nullable
public Set<ComponentName> getBlockedActivities() {
if (mBlockedActivities == null) {
@@ -255,8 +259,10 @@
*
* @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
* in the virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Null and empty have different semantics - Null allows all activities to be streamed
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
if (mBlockedActivities != null && allowedActivities != null) {
throw new IllegalArgumentException(
@@ -279,8 +285,10 @@
*
* @param blockedActivities A set of {@link ComponentName} to be blocked launching from
* virtual device.
- * @hide // TODO(b/194949534): Unhide this API
*/
+ // Allowing null to enforce that at most one of allowed / blocked activities can be non-null
+ @SuppressLint("NullableCollection")
+ @NonNull
public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
if (mAllowedActivities != null && blockedActivities != null) {
throw new IllegalArgumentException(
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ce2efcf..2074125 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
import android.annotation.UiContext;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.BroadcastOptions;
import android.app.GameManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -407,6 +408,7 @@
* @hide
*/
@SystemApi
+ @Deprecated
public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 0x00040000;
/**
@@ -420,12 +422,13 @@
public static final int BIND_SCHEDULE_LIKE_TOP_APP = 0x00080000;
/**
- * This flag has never been used.
+ * Flag for {@link #bindService}: allow background activity starts from the bound service's
+ * process.
+ * This flag is only respected if the caller is holding
+ * {@link android.Manifest.permission#START_ACTIVITIES_FROM_BACKGROUND}.
* @hide
- * @deprecated This flag has never been used.
*/
@SystemApi
- @Deprecated
public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 0x00100000;
/**
@@ -2260,6 +2263,27 @@
}
/**
+ * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to
+ * specify the {@link android.app.BroadcastOptions}.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermissions Array of names of permissions that a receiver must hold
+ * in order to receive your broadcast.
+ * If empty, no permissions are required.
+ * @param options Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @see #sendBroadcastMultiplePermissions(Intent, String[])
+ * @see android.app.BroadcastOptions
+ * @hide
+ */
+ @SystemApi
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) {
+ sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle());
+ }
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an array of required permissions to be enforced. This call is asynchronous; it returns
* immediately, and you will continue executing while the receivers are run. No results are
@@ -3866,7 +3890,6 @@
//@hide: SPEECH_RECOGNITION_SERVICE,
UWB_SERVICE,
MEDIA_METRICS_SERVICE,
- SUPPLEMENTAL_PROCESS_SERVICE,
//@hide: ATTESTATION_VERIFICATION_SERVICE,
//@hide: SAFETY_CENTER_SERVICE,
})
@@ -4979,6 +5002,18 @@
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)
+ */
+ @SystemApi
+ public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
@@ -4997,6 +5032,20 @@
public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
/**
+ * Used for getting the wallpaper effects generation service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(WALLPAPER_EFFECTS_GENERATION_SERVICE)} should check for
+ * {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String WALLPAPER_EFFECTS_GENERATION_SERVICE =
+ "wallpaper_effects_generation";
+
+ /**
* Used to access {@link MusicRecognitionManagerService}.
*
* @hide
@@ -5093,6 +5142,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
@@ -5614,6 +5673,15 @@
public static final String OVERLAY_SERVICE = "overlay";
/**
+ * Use with {@link #getSystemService(String)} to manage resources.
+ *
+ * @see #getSystemService(String)
+ * @see com.android.server.resources.ResourcesManagerService
+ * @hide
+ */
+ public static final String RESOURCES_SERVICE = "resources";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {android.os.IIdmap2} for managing idmap files (used by overlay
* packages).
@@ -5903,13 +5971,6 @@
public static final String LOCALE_SERVICE = "locale";
/**
- * Use with {@link #getSystemService(String)} to retrieve a Supplemental Process Manager.
- *
- * @see #getSystemService(String)
- */
- public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process";
-
- /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.safetycenter.SafetyCenterManager} instance for interacting with the safety center.
*
@@ -6443,15 +6504,26 @@
* <li>Each permission in {@code permissions} must be a runtime permission.
* </ul>
* <p>
- * For every permission in {@code permissions}, the entire permission group it belongs to will
- * be revoked. The revocation happens asynchronously and kills all processes running in the
- * calling UID. It will be triggered once it is safe to do so. In particular, it will not be
- * triggered as long as the package remains in the foreground, or has any active manifest
- * components (e.g. when another app is accessing a content provider in the package).
+ * Background permissions which have no corresponding foreground permission still granted once
+ * the revocation is effective will also be revoked.
+ * <p>
+ * The revocation happens asynchronously and kills all processes running in the calling UID. It
+ * will be triggered once it is safe to do so. In particular, it will not be triggered as long
+ * as the package remains in the foreground, or has any active manifest components (e.g. when
+ * another app is accessing a content provider in the package).
* <p>
* If you want to revoke the permissions right away, you could call {@code System.exit()}, but
* this could affect other apps that are accessing your app at the moment. For example, apps
* accessing a content provider in your app will all crash.
+ * <p>
+ * Note that the settings UI shows a permission group as granted as long as at least one
+ * permission in the group is granted. If you want the user to observe the revocation in the
+ * settings, you should revoke every permission in the target group. To learn the current list
+ * of permissions in a group, you may use
+ * {@link PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer)} and
+ * {@link PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer)}. This list
+ * of permissions may evolve over time, so it is recommended to check whether it contains any
+ * permission you wish to retain before trying to revoke an entire group.
*
* @param permissions Collection of permissions to be revoked.
* @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer)
@@ -6653,21 +6725,27 @@
@NonNull Configuration overrideConfiguration);
/**
- * Returns a new <code>Context</code> object from the current context but with resources
- * adjusted to match the metrics of <code>display</code>. Each call to this method
+ * Returns a new {@code Context} object from the current context but with resources
+ * adjusted to match the metrics of {@code display}. Each call to this method
* returns a new instance of a context object. Context objects are not shared; however,
* common state (such as the {@link ClassLoader} and other resources for the same
- * configuration) can be shared, so the <code>Context</code> itself is lightweight.
+ * configuration) can be shared, so the {@code Context} itself is lightweight.
+ *
+ * <p><b>Note:</b>
+ * This {@code Context} is <b>not</b> expected to be updated with new configuration if the
+ * underlying display configuration changes and the cached {@code Resources} it returns
+ * could be stale. It is suggested to use
+ * {@link android.hardware.display.DisplayManager.DisplayListener} to listen for
+ * changes and re-create an instance if necessary. </p>
* <p>
+ * This {@code Context} is <b>not</b> a UI context, do not use it to access UI components
+ * or obtain a {@link WindowManager} instance.
+ * </p><p>
* To obtain an instance of {@link WindowManager} configured to show windows on the given
* display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
* then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
* returned window context.
- * <p>
- * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
- * context. Do not access UI components or obtain a {@link WindowManager} from the context
- * created by <code>createDisplayContext(Display)</code>.
- *
+ * </p>
* @param display The display to which the current context's resources are adjusted.
*
* @return A context for the display.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7f00bcb..28bef56 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
@@ -4023,7 +3986,7 @@
* {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SystemApi
public static final String ACTION_USER_SWITCHED =
"android.intent.action.USER_SWITCHED";
@@ -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";
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
@@ -5602,6 +5576,7 @@
/**
* A String[] holding attribution tags when used with
* {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ * and ACTION_MANAGE_PERMISSION_USAGE
*
* E.g. an attribution tag could be location_provider, com.google.android.gms.*, etc.
*/
@@ -5610,17 +5585,20 @@
/**
* A long representing the start timestamp (epoch time in millis) of the permission usage
* when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ * and ACTION_MANAGE_PERMISSION_USAGE
*/
public static final String EXTRA_START_TIME = "android.intent.extra.START_TIME";
/**
* A long representing the end timestamp (epoch time in millis) of the permission usage when
* used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ * and ACTION_MANAGE_PERMISSION_USAGE
*/
public static final String EXTRA_END_TIME = "android.intent.extra.END_TIME";
/**
- * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD},
+ * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}
+ * and {@link #ACTION_MANAGE_PERMISSION_USAGE},
* that specifies whether the permission usage system UI is showing attribution information
* for the chosen entry.
*
@@ -6222,6 +6200,8 @@
*
* @hide
*/
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
@@ -6461,77 +6441,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).
@@ -7156,6 +7065,7 @@
*
* @hide
*/
+ @SystemApi
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
/**
* If set, the broadcast will never go to manifest receivers in background (cached
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9e9dd1e..567f649 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@
import static android.os.Build.VERSION_CODES.DONUT;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -48,6 +49,7 @@
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@@ -62,58 +64,58 @@
private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
/**
- * Default task affinity of all activities in this application. See
- * {@link ActivityInfo#taskAffinity} for more information. This comes
- * from the "taskAffinity" attribute.
+ * Default task affinity of all activities in this application. See
+ * {@link ActivityInfo#taskAffinity} for more information. This comes
+ * from the "taskAffinity" attribute.
*/
public String taskAffinity;
-
+
/**
* Optional name of a permission required to be able to access this
* application's components. From the "permission" attribute.
*/
public String permission;
-
+
/**
* The name of the process this application should run in. From the
* "process" attribute or, if not set, the same as
* <var>packageName</var>.
*/
public String processName;
-
+
/**
* Class implementing the Application object. From the "class"
* attribute.
*/
public String className;
-
+
/**
* A style resource identifier (in the package's resources) of the
* description of an application. From the "description" attribute
* or, if not set, 0.
*/
- public int descriptionRes;
-
+ public int descriptionRes;
+
/**
* A style resource identifier (in the package's resources) of the
* default visual theme of the application. From the "theme" attribute
* or, if not set, 0.
*/
public int theme;
-
+
/**
* Class implementing the Application's manage space
* functionality. From the "manageSpaceActivity"
* attribute. This is an optional attribute and will be null if
* applications don't specify it in their manifest
*/
- public String manageSpaceActivityName;
-
+ public String manageSpaceActivityName;
+
/**
* Class implementing the Application's backup functionality. From
* the "backupAgent" attribute. This is an optional attribute and
* will be null if the application does not specify it in its manifest.
- *
+ *
* <p>If android:allowBackup is set to false, this attribute is ignored.
*/
public String backupAgentName;
@@ -174,7 +176,7 @@
* {@code signatureOrSystem}.
*/
public static final int FLAG_SYSTEM = 1<<0;
-
+
/**
* Value for {@link #flags}: set to true if this application would like to
* allow debugging of its
@@ -183,7 +185,7 @@
* android:debuggable} of the <application> tag.
*/
public static final int FLAG_DEBUGGABLE = 1<<1;
-
+
/**
* Value for {@link #flags}: set to true if this application has code
* associated with it. Comes
@@ -191,7 +193,7 @@
* android:hasCode} of the <application> tag.
*/
public static final int FLAG_HAS_CODE = 1<<2;
-
+
/**
* Value for {@link #flags}: set to true if this application is persistent.
* Comes from {@link android.R.styleable#AndroidManifestApplication_persistent
@@ -212,20 +214,20 @@
* android:allowTaskReparenting} of the <application> tag.
*/
public static final int FLAG_ALLOW_TASK_REPARENTING = 1<<5;
-
+
/**
* Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
* Comes from {@link android.R.styleable#AndroidManifestApplication_allowClearUserData
* android:allowClearUserData} of the <application> tag.
*/
public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6;
-
+
/**
* Value for {@link #flags}: this is set if this application has been
* installed as an update to a built-in system application.
*/
public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7;
-
+
/**
* Value for {@link #flags}: this is set if the application has specified
* {@link android.R.styleable#AndroidManifestApplication_testOnly
@@ -240,15 +242,15 @@
* android:smallScreens}.
*/
public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9;
-
+
/**
* Value for {@link #flags}: true when the application's window can be
* displayed on normal screens. Corresponds to
* {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens
* android:normalScreens}.
*/
- public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10;
-
+ public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10;
+
/**
* Value for {@link #flags}: true when the application's window can be
* increased in size for larger screens. Corresponds to
@@ -256,7 +258,7 @@
* android:largeScreens}.
*/
public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11;
-
+
/**
* Value for {@link #flags}: true when the application knows how to adjust
* its UI for different screen sizes. Corresponds to
@@ -264,7 +266,7 @@
* android:resizeable}.
*/
public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12;
-
+
/**
* Value for {@link #flags}: true when the application knows how to
* accommodate different screen densities. Corresponds to
@@ -276,7 +278,7 @@
*/
@Deprecated
public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13;
-
+
/**
* Value for {@link #flags}: set to true if this application would like to
* request the VM to operate under the safe mode. Comes from
@@ -288,7 +290,7 @@
/**
* Value for {@link #flags}: set to <code>false</code> if the application does not wish
* to permit any OS-driven backups of its data; <code>true</code> otherwise.
- *
+ *
* <p>Comes from the
* {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
* attribute of the <application> tag.
@@ -351,7 +353,7 @@
* android:xlargeScreens}.
*/
public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19;
-
+
/**
* Value for {@link #flags}: true when the application has requested a
* large heap for its processes. Corresponds to
@@ -1114,7 +1116,7 @@
* the same uid).
*/
public int uid;
-
+
/**
* The minimum SDK version this application can run on. It will not run
* on earlier versions.
@@ -1817,7 +1819,7 @@
if (sb == null) {
sb = ab.packageName;
}
-
+
return sCollator.compare(sa.toString(), sb.toString());
}
@@ -1830,7 +1832,7 @@
public ApplicationInfo() {
createTimestamp = System.currentTimeMillis();
}
-
+
public ApplicationInfo(ApplicationInfo orig) {
super(orig);
taskAffinity = orig.taskAffinity;
@@ -2125,7 +2127,7 @@
/**
* Disable compatibility mode
- *
+ *
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -2346,7 +2348,7 @@
}
return pm.getDefaultActivityIcon();
}
-
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean isPackageUnavailable(PackageManager pm) {
try {
@@ -2655,4 +2657,22 @@
public int getLocaleConfigRes() {
return localeConfigRes;
}
+
+
+ /**
+ * List of all shared libraries this application is linked against. This
+ * list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+ * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public List<SharedLibraryInfo> getSharedLibraryInfos() {
+ if (sharedLibraryInfos == null) {
+ return Collections.EMPTY_LIST;
+ }
+ return sharedLibraryInfos;
+ }
+
}
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 1e88758..94f0561 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -282,7 +282,8 @@
final boolean isManagedProfile =
mUserManager.isManagedProfile(userHandle.getIdentifier());
if (isManagedProfile) {
- return mResources.getDrawable(R.drawable.ic_corp_badge, null);
+ return mContext.getPackageManager().getUserBadgeForDensityNoBackground(
+ userHandle, /* density= */ 0);
} else {
return UserIcons.getDefaultUserIcon(
mResources, UserHandle.USER_SYSTEM, true /* light */);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1c82b38..30aed8b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -653,6 +653,7 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
String getPermissionControllerPackageName();
+ String getSupplementalProcessPackageName();
ParceledListSlice getInstantApps(int userId);
byte[] getInstantAppCookie(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 9735f81..410e106 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -21,7 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
/**
* Basic information about a package as specified in its manifest.
@@ -80,10 +80,10 @@
/**
* Specifies the recommended install location. Can be one of
- * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
- * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
- * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
- * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors.
+ * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage,
+ * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media,
+ * {@link InstallLocationUtils#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors,
+ * or {@link InstallLocationUtils#RECOMMEND_FAILED_INVALID_APK} for parse errors.
*/
public int recommendedInstallLocation;
public int installLocation;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5b0c275..e9466e9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1063,6 +1063,7 @@
* via this flag.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 0x04000000;
/**
@@ -3154,6 +3155,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.
*/
@@ -3317,7 +3326,8 @@
* <p>This feature should only be defined if {@link #FEATURE_TELEPHONY} has been defined.
*/
@SdkConstant(SdkConstantType.FEATURE)
- public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio";
+ public static final String FEATURE_TELEPHONY_RADIO_ACCESS =
+ "android.hardware.telephony.radio.access";
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
@@ -5683,6 +5693,20 @@
}
/**
+ * Returns the package name of the component implementing supplemental process service.
+ *
+ * @return the package name of the component implementing supplemental process service
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public String getSupplementalProcessPackageName() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Add a new dynamic permission to the system. For this to work, your
* package must have defined a permission tree through the
* {@link android.R.styleable#AndroidManifestPermissionTree
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a5d97f9..bb88486 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -363,6 +363,9 @@
/**
* The group this permission is a part of, as per
* {@link android.R.attr#permissionGroup}.
+ * <p>
+ * The actual grouping of platform-defined runtime permissions is subject to change and can be
+ * queried with {@link PackageManager#getGroupOfPlatformPermission}.
*/
public @Nullable String group;
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/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 43a4b17..4c0e2e6 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -46,7 +46,7 @@
TYPE_BUILTIN,
TYPE_DYNAMIC,
TYPE_STATIC,
- TYPE_SDK,
+ TYPE_SDK_PACKAGE,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type{}
@@ -68,15 +68,21 @@
* Shared library type: this library is <strong>not</strong> backwards
* -compatible, can be updated and updates can be uninstalled. Clients
* link against a specific version of the library.
+ *
+ * Static shared libraries simulate static linking while allowing for
+ * multiple clients to reuse the same instance of the library.
*/
public static final int TYPE_STATIC = 2;
/**
- * SDK library type: this library is <strong>not</strong> backwards
- * -compatible, can be updated and updates can be uninstalled. Clients
- * depend on a specific version of the library.
+ * SDK package shared library type: this library is <strong>not</strong>
+ * compatible between versions, can be updated and updates can be
+ * uninstalled. Clients depend on a specific version of the library.
+ *
+ * SDK packages are not loaded automatically by the OS and rely
+ * e.g. on 3P libraries to make them available for the clients.
*/
- public static final int TYPE_SDK = 3;
+ public static final int TYPE_SDK_PACKAGE = 3;
/**
* Constant for referring to an undefined version.
@@ -301,7 +307,7 @@
* @hide
*/
public boolean isSdk() {
- return mType == TYPE_SDK;
+ return mType == TYPE_SDK_PACKAGE;
}
/**
@@ -367,7 +373,7 @@
case TYPE_STATIC: {
return "static";
}
- case TYPE_SDK: {
+ case TYPE_SDK_PACKAGE: {
return "sdk";
}
default: {
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index a503d14..dea0834 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -162,5 +162,68 @@
{
"name": "CtsInstallHostTestCases"
}
+ ],
+ "staged-platinum-postsubmit": [
+ {
+ "name": "CtsIncrementalInstallHostTestCases"
+ },
+ {
+ "name": "CtsInstallHostTestCases"
+ },
+ {
+ "name": "CtsStagedInstallHostTestCases"
+ },
+ {
+ "name": "CtsExtractNativeLibsHostTestCases"
+ },
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.android.cts.splitapp.SplitAppTest"
+ },
+ {
+ "include-filter": "android.appsecurity.cts.EphemeralTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.PackageParserTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsRollbackManagerHostTestCases"
+ },
+ {
+ "name": "CtsOsHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.PackageParserTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.cts.IntentFilterTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppEnumerationTestCases"
+ },
+ {
+ "name": "PackageManagerServiceUnitTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.verify.domain"
+ }
+ ]
+ }
]
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 6fd2d05..7a5ac8e 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -28,6 +28,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -438,6 +439,12 @@
}
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "debugName=" + getDebugName());
+ pw.println(prefix + "assetPath=" + getAssetPath());
+ }
+
private static native long nativeLoad(@FormatType int format, @NonNull String path,
@PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
private static native long nativeLoadEmpty(@PropertyFlags int flags,
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bfd9fd0..a05f5c9 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -43,6 +43,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1531,6 +1532,15 @@
}
}
+ synchronized void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "apkAssets=");
+ for (int i = 0; i < mApkAssets.length; i++) {
+ pw.println(prefix + i);
+ mApkAssets[i].dump(pw, prefix + " ");
+ }
+ }
+
// AssetManager setup native methods.
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/content/res/IResourcesManager.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/content/res/IResourcesManager.aidl
index 861a4ed..d137378 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/content/res/IResourcesManager.aidl
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.content.res;
-parcelable DeviceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * Api for getting information about resources.
+ *
+ * {@hide}
+ */
+interface IResourcesManager {
+ boolean dumpResources(in String process,
+ in ParcelFileDescriptor fd,
+ in RemoteCallback finishCallback);
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cb53a2a..ebef053 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -53,6 +53,7 @@
import android.graphics.drawable.DrawableInflater;
import android.os.Build;
import android.os.Bundle;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -78,12 +79,15 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -143,9 +147,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();
@@ -176,6 +177,11 @@
private int mBaseApkAssetsSize;
+ /** @hide */
+ private static Set<Resources> sResourcesHistory = Collections.synchronizedSet(
+ Collections.newSetFromMap(
+ new WeakHashMap<>()));
+
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
@@ -322,6 +328,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Resources(@Nullable ClassLoader classLoader) {
mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
+ sResourcesHistory.add(this);
}
/**
@@ -2080,7 +2087,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 +2181,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;
}
/**
@@ -2676,4 +2660,29 @@
}
}
}
+
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "resourcesImpl");
+ mResourcesImpl.dump(pw, prefix + " ");
+ }
+
+ /** @hide */
+ public static void dumpHistory(PrintWriter pw, String prefix) {
+ pw.println(prefix + "history");
+ // Putting into a map keyed on the apk assets to deduplicate resources that are different
+ // objects but ultimately represent the same assets
+ Map<List<ApkAssets>, Resources> history = new ArrayMap<>();
+ for (Resources r : sResourcesHistory) {
+ history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r);
+ }
+ int i = 0;
+ for (Resources r : history.values()) {
+ if (r != null) {
+ pw.println(prefix + i++);
+ r.dump(pw, prefix + " ");
+ }
+ }
+ }
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4d850b0..ff07291 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -61,6 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Locale;
@@ -1271,6 +1272,12 @@
NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(),
AssetManager.getThemeFreeFunction());
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "class=" + getClass());
+ pw.println(prefix + "assets");
+ mAssets.dump(pw, prefix + " ");
+ }
+
public class ThemeImpl {
/**
* Unique key for the series of styles applied to this theme.
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/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index f13c795..52bba14 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -131,11 +131,16 @@
*
* @param name The name of the cursor window, or null if none.
* @param windowSizeBytes Size of cursor window in bytes.
+ * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0
+ * @throws AssertionError if created window pointer is 0
* <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
* window. Depending on the amount of data stored, the actual amount of memory allocated can be
* lower than specified size, but cannot exceed it.
*/
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
+ if (windowSizeBytes < 0) {
+ throw new IllegalArgumentException("Window size cannot be less than 0");
+ }
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
index 2e3227d..5315c8b 100644
--- a/core/java/android/database/CursorWindowAllocationException.java
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -16,14 +16,14 @@
package android.database;
+import android.annotation.NonNull;
+
/**
* This exception is thrown when a CursorWindow couldn't be allocated,
* most probably due to memory not being available.
- *
- * @hide
*/
public class CursorWindowAllocationException extends RuntimeException {
- public CursorWindowAllocationException(String description) {
+ public CursorWindowAllocationException(@NonNull String description) {
super(description);
}
}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index acceb65..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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
similarity index 61%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/hardware/biometrics/IBiometricContextListener.aidl
index 861a4ed..55cab52 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl
@@ -13,7 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.biometrics;
-package com.android.systemui.shared.mediattt;
-
-parcelable DeviceInfo;
+/**
+ * A secondary communication channel from AuthController back to BiometricService for
+ * events that are not associated with an autentication session. See
+ * {@link IBiometricSysuiReceiver} for events associated with a session.
+ *
+ * @hide
+ */
+oneway interface IBiometricContextListener {
+ void onDozeChanged(boolean isDozing);
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c12e819..d6d3a97 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -25,6 +25,7 @@
import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Point;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -458,12 +459,14 @@
(DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
if (display != null) {
- int width = display.getWidth();
- int height = display.getHeight();
+ Point sz = new Point();
+ display.getRealSize(sz);
+ int width = sz.x;
+ int height = sz.y;
if (height > width) {
height = width;
- width = display.getHeight();
+ width = sz.y;
}
ret = new Size(width, height);
@@ -471,7 +474,7 @@
Log.e(TAG, "Invalid default display!");
}
} catch (Exception e) {
- Log.e(TAG, "getDisplaySize Failed. " + e.toString());
+ Log.e(TAG, "getDisplaySize Failed. " + e);
}
return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 2920e67..87553d8 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -30,9 +30,11 @@
import android.os.Handler;
import android.os.ConditionVariable;
import android.util.Range;
+import android.util.Log;
import android.view.Surface;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -56,6 +58,7 @@
private final CameraCharacteristics mCharacteristics;
private final CameraCaptureSessionImpl mSessionImpl;
private final ConditionVariable mInitialized = new ConditionVariable();
+ private final String TAG = "CameraConstrainedHighSpeedCaptureSessionImpl";
/**
* Create a new CameraCaptureSession.
@@ -95,10 +98,33 @@
StreamConfigurationMap config = mCharacteristics.get(ck);
SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange, config);
- // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize
- // the preview frame rate, should use maxBatch size for that high speed stream
- // configuration. We choose the former for now.
- int requestListSize = fpsRange.getUpper() / 30;
+ // Check the high speed video fps ranges for video size and find the min value from the list
+ // and assign it to previewFps which will be used to calculate the requestList size.
+ Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRangesFor(
+ SurfaceUtils.getSurfaceSize(outputSurfaces.iterator().next()));
+ Log.v(TAG, "High speed fps ranges: " + Arrays.toString(highSpeedFpsRanges));
+ int previewFps = Integer.MAX_VALUE;
+ for (Range<Integer> range : highSpeedFpsRanges) {
+ int rangeMin = range.getLower();
+ if (previewFps > rangeMin) {
+ previewFps = rangeMin;
+ }
+ }
+ // Since we only want to support 60fps apart from 30fps, if the min value is not 60,
+ // then continue to calculate the requestList size using value 30.
+ if (previewFps != 60 && previewFps != 30) {
+ Log.w(TAG, "previewFps is neither 60 nor 30.");
+ previewFps = 30;
+ }
+ Log.v(TAG, "previewFps: " + previewFps);
+
+ int requestListSize = fpsRange.getUpper() / previewFps;
+ // If it's a preview, keep requestList size fixed = 1.
+ if (fpsRange.getUpper() > fpsRange.getLower()) {
+ requestListSize = 1;
+ }
+
+ Log.v(TAG, "Request list size is: " + requestListSize);
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
// Prepare the Request builders: need carry over the request controls.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index b06d076..30aa4db 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -79,9 +79,9 @@
* <ul>
* <li>The system deems the request can no longer be honored, for example if the requested
* state becomes unsupported.
- * <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
+ * <li>A call to {@link #cancelStateRequest}.
* <li>Another processes submits a request succeeding this request in which case the request
- * will be suspended until the interrupting request is canceled.
+ * will be canceled.
* </ul>
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
*
@@ -100,19 +100,18 @@
}
/**
- * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+ * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
* <p>
- * This method is noop if the {@code request} has not been submitted with a call to
- * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+ * This method is noop if there is no request currently active.
*
* @throws SecurityException if the caller is neither the current top-focused activity nor if
* the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
*/
@RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
- public void cancelRequest(@NonNull DeviceStateRequest request) {
- mGlobal.cancelRequest(request);
+ public void cancelStateRequest() {
+ mGlobal.cancelStateRequest();
}
/**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 85e70b0..aba538f 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -151,20 +151,14 @@
* Cancels a {@link DeviceStateRequest request} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*
- * @see DeviceStateManager#cancelRequest(DeviceStateRequest)
+ * @see DeviceStateManager#cancelStateRequest
*/
- public void cancelRequest(@NonNull DeviceStateRequest request) {
+ public void cancelStateRequest() {
synchronized (mLock) {
registerCallbackIfNeededLocked();
- final IBinder token = findRequestTokenLocked(request);
- if (token == null) {
- // This request has not been submitted.
- return;
- }
-
try {
- mDeviceStateManager.cancelRequest(token);
+ mDeviceStateManager.cancelStateRequest();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -299,20 +293,6 @@
/**
* Handles a call from the server that a request for the supplied {@code token} has become
- * suspended.
- */
- private void handleRequestSuspended(IBinder token) {
- DeviceStateRequestWrapper request;
- synchronized (mLock) {
- request = mRequests.get(token);
- }
- if (request != null) {
- request.notifyRequestSuspended();
- }
- }
-
- /**
- * Handles a call from the server that a request for the supplied {@code token} has become
* canceled.
*/
private void handleRequestCanceled(IBinder token) {
@@ -337,11 +317,6 @@
}
@Override
- public void onRequestSuspended(IBinder token) {
- handleRequestSuspended(token);
- }
-
- @Override
public void onRequestCanceled(IBinder token) {
handleRequestCanceled(token);
}
@@ -395,14 +370,6 @@
mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
}
- void notifyRequestSuspended() {
- if (mCallback == null) {
- return;
- }
-
- mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
- }
-
void notifyRequestCanceled() {
if (mCallback == null) {
return;
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
index df488d2..893d765 100644
--- a/core/java/android/hardware/devicestate/DeviceStateRequest.java
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -32,8 +32,7 @@
* DeviceStateRequest.Callback)}.
* <p>
* By default, the request is kept active until a call to
- * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
- * occurs:
+ * {@link DeviceStateManager#cancelStateRequest} or until one of the following occurs:
* <ul>
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 14ed03d..e450e42 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,8 +41,9 @@
* previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
* call to this method.
*
- * @param token the request token previously registered with
- * {@link #requestState(IBinder, int, int)}
+ * @param token the request token provided
+ * @param state the state of device the request is asking for
+ * @param flags any flags that correspond to the request
*
* @throws IllegalStateException if a callback has not yet been registered for the calling
* process.
@@ -52,14 +53,11 @@
void requestState(IBinder token, int state, int flags);
/**
- * Cancels a request previously submitted with a call to
+ * Cancels the active request previously submitted with a call to
* {@link #requestState(IBinder, int, int)}.
*
- * @param token the request token previously registered with
- * {@link #requestState(IBinder, int, int)}
- *
- * @throws IllegalStateException if the supplied {@code token} has not been previously
- * requested.
+ * @throws IllegalStateException if a callback has not yet been registered for the calling
+ * process.
*/
- void cancelRequest(IBinder token);
+ void cancelStateRequest();
}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
index efb9888..348690f 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
@@ -41,16 +41,6 @@
oneway void onRequestActive(IBinder token);
/**
- * Called to notify the callback that a request has become suspended. Guaranteed to be called
- * before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
- * becoming suspended resulted in a change of device state info.
- *
- * @param token the request token previously registered with
- * {@link IDeviceStateManager#requestState(IBinder, int, int)}
- */
- oneway void onRequestSuspended(IBinder token);
-
- /**
* Called to notify the callback that a request has become canceled. No further callbacks will
* be triggered for this request. Guaranteed to be called before a subsequent call to
* {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request becoming canceled resulted
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 0dc8f92..99f3d15 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -57,6 +57,23 @@
*/
public static final int HIGH_BRIGHTNESS_MODE_HDR = 2;
+ @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
+ BRIGHTNESS_MAX_REASON_NONE,
+ BRIGHTNESS_MAX_REASON_THERMAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BrightnessMaxReason {}
+
+ /**
+ * Maximum brightness is unrestricted.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_NONE = 0;
+
+ /**
+ * Maximum brightness is restricted due to thermal throttling.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
+
/** Brightness */
public final float brightness;
@@ -78,21 +95,29 @@
*/
public final int highBrightnessMode;
+ /**
+ * The current reason for restricting maximum brightness.
+ * Can be any of BRIGHTNESS_MAX_REASON_* values.
+ */
+ public final int brightnessMaxReason;
+
public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
- @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+ @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint,
+ @BrightnessMaxReason int brightnessMaxReason) {
this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
- highBrightnessTransitionPoint);
+ highBrightnessTransitionPoint, brightnessMaxReason);
}
public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
- float highBrightnessTransitionPoint) {
+ float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) {
this.brightness = brightness;
this.adjustedBrightness = adjustedBrightness;
this.brightnessMinimum = brightnessMinimum;
this.brightnessMaximum = brightnessMaximum;
this.highBrightnessMode = highBrightnessMode;
this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
+ this.brightnessMaxReason = brightnessMaxReason;
}
/**
@@ -110,6 +135,19 @@
return "invalid";
}
+ /**
+ * @return User-friendly string for specified {@link BrightnessMaxReason} parameter.
+ */
+ public static String briMaxReasonToString(@BrightnessMaxReason int reason) {
+ switch (reason) {
+ case BRIGHTNESS_MAX_REASON_NONE:
+ return "none";
+ case BRIGHTNESS_MAX_REASON_THERMAL:
+ return "thermal";
+ }
+ return "invalid";
+ }
+
@Override
public int describeContents() {
return 0;
@@ -123,6 +161,7 @@
dest.writeFloat(brightnessMaximum);
dest.writeInt(highBrightnessMode);
dest.writeFloat(highBrightnessTransitionPoint);
+ dest.writeInt(brightnessMaxReason);
}
public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -145,6 +184,7 @@
brightnessMaximum = source.readFloat();
highBrightnessMode = source.readInt();
highBrightnessTransitionPoint = source.readFloat();
+ brightnessMaxReason = source.readInt();
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1a7a63ae..af8ec27 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -916,6 +916,17 @@
}
/**
+ * Returns the system preferred display mode.
+ */
+ public Display.Mode getSystemPreferredDisplayMode(int displayId) {
+ try {
+ return mDm.getSystemPreferredDisplayMode(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* When enabled the app requested display resolution and refresh rate is always selected
* in DisplayModeDirector regardless of user settings and policies for low brightness, low
* battery etc.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 35663af..b3af52b 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -168,6 +168,7 @@
// Requires MODIFY_USER_PREFERRED_DISPLAY_MODE permission.
void setUserPreferredDisplayMode(int displayId, in Mode mode);
Mode getUserPreferredDisplayMode(int displayId);
+ Mode getSystemPreferredDisplayMode(int displayId);
// When enabled the app requested display resolution and refresh rate is always selected
// in DisplayModeDirector regardless of user settings and policies for low brightness, low
diff --git a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
index e7d76f6..bb34646 100644
--- a/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
+++ b/core/java/android/hardware/hdmi/HdmiAudioSystemClient.java
@@ -142,7 +142,7 @@
* @hide
*/
public void setSystemAudioMode(boolean state, @NonNull SetSystemAudioModeCallback callback) {
- // TODO(amyjojo): implement this when needed.
+ // TODO(b/217509829): implement this when needed.
}
/**
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index cee6a5b..b96e4f8 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -21,6 +21,8 @@
public abstract class HdmiClient {
private static final String TAG = "HdmiClient";
+ private static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
+
/* package */ final IHdmiControlService mService;
private IHdmiVendorCommandListener mIHdmiVendorCommandListener;
@@ -156,11 +158,25 @@
}
/**
- * Sets a listener used to receive incoming vendor-specific command.
+ * Sets a listener used to receive incoming vendor-specific command. This listener will only
+ * receive {@code <Vendor Command>} but will not receive any {@code <Vendor Command with ID>}
+ * messages.
*
* @param listener listener object
*/
public void setVendorCommandListener(@NonNull VendorCommandListener listener) {
+ // Set the vendor ID to INVALID_VENDOR_ID.
+ setVendorCommandListener(listener, UNKNOWN_VENDOR_ID);
+ }
+
+ /**
+ * Sets a listener used to receive incoming vendor-specific command.
+ *
+ * @param listener listener object
+ * @param vendorId The listener is interested in {@code <Vendor Command with ID>} received with
+ * this vendorId and all {@code <Vendor Command>} messages.
+ */
+ public void setVendorCommandListener(@NonNull VendorCommandListener listener, int vendorId) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
@@ -169,7 +185,7 @@
}
try {
IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener);
- mService.addVendorCommandListener(wrappedListener, getDeviceType());
+ mService.addVendorCommandListener(wrappedListener, vendorId);
mIHdmiVendorCommandListener = wrappedListener;
} catch (RemoteException e) {
Log.e(TAG, "failed to set vendor command listener: ", e);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 5874385..9235ba1 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -105,6 +105,12 @@
"android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
/**
+ * Used as an extra field in the Set Menu Language intent. Contains the requested locale.
+ * @hide
+ */
+ public static final String EXTRA_LOCALE = "android.hardware.hdmi.extra.LOCALE";
+
+ /**
* Volume value for mute state.
*/
public static final int AVR_VOLUME_MUTED = 101;
@@ -558,6 +564,32 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TvSendStandbyOnSleep {}
+ // -- Whether a playback device should act on an incoming {@code <Set Menu Language>} message.
+ /**
+ * Confirmation dialog should be shown upon receiving the CEC message.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ public static final int SET_MENU_LANGUAGE_ENABLED = 1;
+ /**
+ * The message should be ignored.
+ *
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ public static final int SET_MENU_LANGUAGE_DISABLED = 0;
+ /**
+ * @see HdmiControlManager#CEC_SETTING_NAME_SET_MENU_LANGUAGE
+ * @hide
+ */
+ @IntDef(prefix = { "SET_MENU_LANGUAGE_" }, value = {
+ SET_MENU_LANGUAGE_ENABLED,
+ SET_MENU_LANGUAGE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SetMenuLanguage {}
+
// -- The RC profile of a TV panel.
/**
* RC profile none.
@@ -812,6 +844,13 @@
public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
"tv_send_standby_on_sleep";
/**
+ * Name of a setting deciding whether {@code <Set Menu Language>} message should be
+ * handled by the framework or ignored.
+ *
+ * @hide
+ */
+ public static final String CEC_SETTING_NAME_SET_MENU_LANGUAGE = "set_menu_language";
+ /**
* Name of a setting representing the RC profile of a TV panel.
*
* @hide
@@ -977,6 +1016,7 @@
CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ CEC_SETTING_NAME_SET_MENU_LANGUAGE,
CEC_SETTING_NAME_RC_PROFILE_TV,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 16adee9..818554d 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -221,8 +221,8 @@
}
@Override
- public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
- HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType);
+ public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+ HdmiControlServiceWrapper.this.addVendorCommandListener(listener, vendorId);
}
@Override
@@ -481,7 +481,7 @@
boolean hasVendorId) {}
/** @hide */
- public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {}
+ public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {}
/** @hide */
public void sendStandby(int deviceType, int deviceId) {}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 6613397..35dd9ed 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -76,7 +76,7 @@
void askRemoteDeviceToBecomeActiveSource(int physicalAddress);
void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
boolean hasVendorId);
- void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
+ void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId);
void sendStandby(int deviceType, int deviceId);
void setHdmiRecordListener(IHdmiRecordListener callback);
void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 0304815..e1ffd4a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -112,7 +112,7 @@
oneway void requestPointerCapture(IBinder inputChannelToken, boolean enabled);
/** Create an input monitor for gestures. */
- InputMonitor monitorGestureInput(String name, int displayId);
+ InputMonitor monitorGestureInput(IBinder token, String name, int displayId);
// Add a runtime association between the input port and the display port. This overrides any
// static associations.
@@ -122,9 +122,9 @@
void removePortAssociation(in String inputPort);
// Add a runtime association between the input device and display.
- void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId);
+ void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
// Remove the runtime association between the input device and display.
- void removeUniqueIdAssociation(in String inputDeviceName);
+ void removeUniqueIdAssociation(in String inputPort);
InputSensorInfo[] getSensorList(int deviceId);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cbc8373..2fd79cf 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -35,6 +35,7 @@
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
+import android.os.Binder;
import android.os.BlockUntrustedTouchesMode;
import android.os.Build;
import android.os.CombinedVibration;
@@ -1211,7 +1212,7 @@
*/
public InputMonitor monitorGestureInput(String name, int displayId) {
try {
- return mIm.monitorGestureInput(name, displayId);
+ return mIm.monitorGestureInput(new Binder(), name, displayId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -1359,19 +1360,18 @@
}
/**
- * Add a runtime association between the input device name and display, by unique id. Input
- * device names are expected to be unique.
- * @param inputDeviceName The name of the input device.
+ * Add a runtime association between the input port and display, by unique id. Input ports are
+ * expected to be unique.
+ * @param inputPort The port of the input device.
* @param displayUniqueId The unique id of the associated display.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
try {
- mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId);
+ mIm.addUniqueIdAssociation(inputPort, displayUniqueId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1379,15 +1379,15 @@
/**
* Removes a runtime association between the input device and display.
- * @param inputDeviceName The name of the input device.
+ * @param inputPort The port of the input device.
* <p>
* Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
try {
- mIm.removeUniqueIdAssociation(inputDeviceName);
+ mIm.removeUniqueIdAssociation(inputPort);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
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/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 60f5135..7ff74c6 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1341,7 +1341,6 @@
*
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
boolean resetUsbPort(@NonNull UsbPort port, int operationId,
IUsbOperationInternal callback) {
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index cc325cd..af57f79 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -70,6 +70,7 @@
private static final int DO_SET_INPUT_CONTEXT = 20;
private static final int DO_UNSET_INPUT_CONTEXT = 30;
private static final int DO_START_INPUT = 32;
+ private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35;
private static final int DO_CREATE_SESSION = 40;
private static final int DO_SET_SESSION_ENABLED = 45;
private static final int DO_SHOW_SOFT_INPUT = 60;
@@ -78,6 +79,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;
@@ -174,7 +176,7 @@
try {
inputMethod.initializeInternal((IBinder) args.arg1,
(IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
- (boolean) args.arg3);
+ (boolean) args.arg3, msg.arg2 != 0);
} finally {
args.recycle();
}
@@ -194,14 +196,22 @@
final EditorInfo info = (EditorInfo) args.arg3;
final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
final boolean restarting = args.argi5 == 1;
+ final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0;
final InputConnection ic = inputContext != null
? new RemoteInputConnection(mTarget, inputContext, cancellationGroup)
: null;
info.makeCompatible(mTargetSdkVersion);
- inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken);
+ inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
+ shouldShowImeSwitcherWhenImeIsShown);
args.recycle();
return;
}
+ case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: {
+ final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0;
+ inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShown);
+ return;
+ }
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
@@ -245,11 +255,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);
@@ -286,10 +300,11 @@
@BinderThread
@Override
public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported) {
- mCaller.executeOrSendMessage(
- mCaller.obtainMessageIOOO(
- DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported));
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
+ configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps,
+ stylusHwSupported));
}
@BinderThread
@@ -329,13 +344,23 @@
@BinderThread
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
- EditorInfo attribute, boolean restarting) {
+ EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
- inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */));
+ inputContext, attribute, mCancellationGroup, restarting ? 1 : 0,
+ shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
+ }
+
+ @BinderThread
+ @Override
+ public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageI(
+ DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED,
+ shouldShowImeSwitcherWhenImeIsShown ? 1 : 0));
}
@BinderThread
@@ -393,10 +418,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..223b8cc 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,
@@ -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();
@@ -652,7 +658,7 @@
@Override
public final void initializeInternal(@NonNull IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported) {
+ boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (mDestroyed) {
Log.i(TAG, "The InputMethodService has already onDestroyed()."
+ "Ignore the initialization.");
@@ -665,6 +671,8 @@
if (stylusHwSupported) {
mInkWindow = new InkWindow(mWindow.getContext());
}
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
attachToken(token);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -736,6 +744,10 @@
onUnbindInput();
mInputBinding = null;
mInputConnection = null;
+ // free-up cached InkWindow surface on detaching from current client.
+ if (mInkWindow != null) {
+ mInkWindow.hide(true /* remove */);
+ }
}
/**
@@ -770,9 +782,10 @@
@Override
public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
mPrivOps.reportStartInputAsync(startInputToken);
-
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
@@ -786,6 +799,18 @@
*/
@MainThread
@Override
+ public void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown(
+ shouldShowImeSwitcherWhenImeIsShown);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
IBinder hideInputToken) {
mSystemCallingHideSoftInput = true;
@@ -888,7 +913,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 +921,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 +940,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();
}
/**
@@ -1448,7 +1504,7 @@
Context.LAYOUT_INFLATER_SERVICE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
-
+ mNavigationBarController.onSoftInputWindowCreated(mWindow);
{
final Window window = mWindow.getWindow();
{
@@ -2175,10 +2231,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 +2353,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 +2428,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..83fc727 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -17,7 +17,10 @@
package android.inputmethodservice;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.StatusBarManager;
@@ -26,6 +29,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
@@ -40,7 +44,10 @@
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
+import android.view.WindowInsetsController.Appearance;
import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import java.util.Objects;
@@ -59,6 +66,9 @@
@NonNull ViewTreeObserver.InternalInsetsInfo dest) {
}
+ default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ }
+
default void onViewInitialized() {
}
@@ -68,6 +78,10 @@
default void onDestroy() {
}
+ default void setShouldShowImeSwitcherWhenImeIsShown(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ }
+
default String toDebugString() {
return "No-op implementation";
}
@@ -88,6 +102,10 @@
mImpl.updateTouchableInsets(originalInsets, dest);
}
+ void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ mImpl.onSoftInputWindowCreated(softInputWindow);
+ }
+
void onViewInitialized() {
mImpl.onViewInitialized();
}
@@ -100,11 +118,21 @@
mImpl.onDestroy();
}
+ void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) {
+ mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown);
+ }
+
String toDebugString() {
return mImpl.toDebugString();
}
- private static final class Impl implements Callback {
+ private static final class Impl implements Callback, Window.DecorCallback {
+ private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
+
+ // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
+ private static final Interpolator LEGACY_DECELERATE =
+ new PathInterpolator(0f, 0f, 0.2f, 1f);
+
@NonNull
private final InputMethodService mService;
@@ -120,6 +148,19 @@
@Nullable
private BroadcastReceiver mSystemOverlayChangedReceiver;
+ private boolean mShouldShowImeSwitcherWhenImeIsShown;
+
+ @Appearance
+ private int mAppearance;
+
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private float mDarkIntensity;
+
+ @Nullable
+ private ValueAnimator mTintAnimator;
+
+ private boolean mDrawLegacyNavigationBarBackground;
+
Impl(@NonNull InputMethodService inputMethodService) {
mService = inputMethodService;
}
@@ -177,7 +218,9 @@
// TODO(b/213337792): Support InputMethodService#setBackDisposition().
// TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
- | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+ | (mShouldShowImeSwitcherWhenImeIsShown
+ ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN
+ : 0);
navigationBarView.setNavigationIconHints(hints);
}
} else {
@@ -186,7 +229,14 @@
mLastInsets = systemInsets;
}
- mNavigationBarFrame.setBackground(null);
+ if (mDrawLegacyNavigationBarBackground) {
+ mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+ } else {
+ mNavigationBarFrame.setBackground(null);
+ }
+
+ setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
+ mDrawLegacyNavigationBarBackground));
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -266,34 +316,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) {
@@ -304,6 +370,13 @@
}
@Override
+ public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
+ final Window window = softInputWindow.getWindow();
+ mAppearance = window.getSystemBarAppearance();
+ window.setDecorCallback(this);
+ }
+
+ @Override
public void onViewInitialized() {
if (mDestroyed) {
return;
@@ -337,6 +410,10 @@
if (mDestroyed) {
return;
}
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ mTintAnimator = null;
+ }
if (mSystemOverlayChangedReceiver != null) {
mService.unregisterReceiver(mSystemOverlayChangedReceiver);
mSystemOverlayChangedReceiver = null;
@@ -373,9 +450,107 @@
}
@Override
+ public void setShouldShowImeSwitcherWhenImeIsShown(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ if (mDestroyed) {
+ return;
+ }
+ if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) {
+ return;
+ }
+ mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+ final NavigationBarView navigationBarView =
+ mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
+ if (navigationBarView == null) {
+ return;
+ }
+ final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+ | (shouldShowImeSwitcherWhenImeIsShown
+ ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0);
+ navigationBarView.setNavigationIconHints(hints);
+ }
+
+ @Override
+ public void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ if (mDestroyed) {
+ return;
+ }
+
+ mAppearance = appearance;
+
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+
+ final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
+ mDrawLegacyNavigationBarBackground);
+
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ }
+ mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
+ mTintAnimator.addUpdateListener(
+ animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
+ mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
+ mTintAnimator.setStartDelay(0);
+ mTintAnimator.setInterpolator(LEGACY_DECELERATE);
+ mTintAnimator.start();
+ }
+
+ private void setIconTintInternal(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+ 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,
+ boolean drawLegacyNavigationBarBackground) {
+ final boolean lightNavBar = !drawLegacyNavigationBarBackground
+ && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+ return lightNavBar ? 1.0f : 0.0f;
+ }
+
+ @Override
+ public boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground) {
+ if (mDestroyed) {
+ return false;
+ }
+
+ if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
+ mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
+ if (mDrawLegacyNavigationBarBackground) {
+ mNavigationBarFrame.setBackgroundColor(Color.BLACK);
+ } else {
+ mNavigationBarFrame.setBackground(null);
+ }
+ scheduleRelayout();
+ onSystemBarAppearanceChanged(mAppearance);
+ }
+ return drawLegacyNavigationBarBackground;
+ }
+
+ @Override
public String toDebugString() {
return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ + " mShouldShowImeSwitcherWhenImeIsShown="
+ + mShouldShowImeSwitcherWhenImeIsShown
+ + " mAppearance=0x" + Integer.toHexString(mAppearance)
+ + " mDarkIntensity=" + mDarkIntensity
+ + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
+ "}";
}
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 6c8eb41..5704dac 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;
@@ -47,6 +46,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 +120,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 +129,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;
}
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
index cd85736..4adc84b 100644
--- a/core/java/android/inputmethodservice/navigationbar/DeadZone.java
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -27,6 +27,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.SystemClock;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -46,6 +47,20 @@
public static final int VERTICAL = 1; // Consume taps along the left edge.
private static final boolean CHATTY = true; // print to logcat when we eat a click
+
+ private static final FloatProperty<DeadZone> FLASH_PROPERTY =
+ new FloatProperty<DeadZone>("DeadZoneFlash") {
+ @Override
+ public void setValue(DeadZone object, float value) {
+ object.setFlash(value);
+ }
+
+ @Override
+ public Float get(DeadZone object) {
+ return object.getFlash();
+ }
+ };
+
private final NavigationBarView mNavigationBarView;
private boolean mShouldFlash;
@@ -63,7 +78,7 @@
private final Runnable mDebugFlash = new Runnable() {
@Override
public void run() {
- ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ ObjectAnimator.ofFloat(DeadZone.this, FLASH_PROPERTY, 1f, 0f).setDuration(150).start();
}
};
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/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index c936bfa..9122adf 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -523,9 +523,11 @@
*
* @param subId the subscriber to get the subscription plans for.
* @param callingPackage the name of the package making the call.
+ * @return the active {@link SubscriptionPlan}s for the given subscription id, or
+ * {@code null} if not found.
* @hide
*/
- @NonNull
+ @Nullable
public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
try {
return mService.getSubscriptionPlans(subId, callingPackage);
@@ -538,7 +540,7 @@
* Get subscription plan for the given networkTemplate.
*
* @param template the networkTemplate to get the subscription plan for.
- * @return the active {@link SubscriptionPlan} for the given template, or
+ * @return the active {@link SubscriptionPlan}s for the given template, or
* {@code null} if not found.
* @hide
*/
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..9772bde
--- /dev/null
+++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.annotation.SystemApi.Client.MODULE_LIBRARIES;
+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.annotation.SystemApi;
+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 {
+ /**
+ * Prefix of the files which are used to store per network interface statistics.
+ */
+ public static final String PREFIX_XT = "xt";
+ /**
+ * Prefix of the files which are used to store per uid statistics.
+ */
+ public static final String PREFIX_UID = "uid";
+ /**
+ * Prefix of the files which are used to store per uid tagged traffic statistics.
+ */
+ public static final String PREFIX_UID_TAG = "uid_tag";
+
+ 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 the implementation to read legacy network stats
+ * from disk. It is used for migration of legacy network stats into the
+ * stats provided by the Connectivity module.
+ * This function needs to know about the previous format(s) of the network
+ * stats data that might be stored on this device so it can be read and
+ * conserved upon upgrade to Android 13 or above.
+ *
+ * This function will be called multiple times sequentially, all on the
+ * same thread, and will not be called multiple times concurrently. This
+ * function is expected to do a substantial amount of disk access, and
+ * doesn't need to return particularly fast, but the first boot after
+ * an upgrade to Android 13+ will be held until migration is done. As
+ * migration is only necessary once, after the first boot following the
+ * upgrade, this delay is not incurred.
+ *
+ * If this function fails in any way, it should throw an exception. If this
+ * happens, the system can't know about the data that was stored in the
+ * legacy files, but it will still count data usage happening on this
+ * session. On the next boot, the system will try migration again, and
+ * merge the returned data with the data used with the previous session.
+ * The system will only try the migration up to three (3) times. The remaining
+ * count is stored in the netstats_import_legacy_file_needed device config. The
+ * legacy data is never deleted by the mainline module to avoid any possible
+ * data loss.
+ *
+ * It is possible to set the netstats_import_legacy_file_needed device config
+ * to any positive integer to force the module to perform the migration. This
+ * can be achieved by calling the following command before rebooting :
+ * adb shell device_config put connectivity netstats_import_legacy_file_needed 1
+ *
+ * The AOSP implementation provides code to read persisted network stats as
+ * they were written by AOSP prior to Android 13.
+ * OEMs who have used the AOSP implementation of persisting network stats
+ * to disk don't need to change anything.
+ * OEM that had modifications to this format should modify this function
+ * to read from their 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 readPlatformCollection(
+ @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 = NetworkTemplate.OEM_MANAGED_NO;
+ }
+
+ // 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/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2d33817..07a5132 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -34,6 +34,7 @@
import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
import android.service.batterystats.BatteryStatsServiceDumpProto;
import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -2654,6 +2655,46 @@
*/
public abstract Timer getPhoneDataConnectionTimer(int dataType);
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2;
+ /** @hide */
+ public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_",
+ value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE,
+ RADIO_ACCESS_TECHNOLOGY_NR})
+ public @interface RadioAccessTechnology {
+ }
+
+ /** @hide */
+ public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"};
+
+ /**
+ * Returns the time in microseconds that the mobile radio has been active on a
+ * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+ * transmission power level.
+ *
+ * @param rat Radio Access Technology {@see RadioAccessTechnology}
+ * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+ * RADIO_ACCESS_TECHNOLOGY_NR. Use
+ * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+ * Technologies.
+ * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+ * @param elapsedRealtimeMs current elapsed realtime
+ * @return time (in milliseconds) the mobile radio spent active in the specified state,
+ * while on battery.
+ * @hide
+ */
+ public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs);
+
static final String[] WIFI_SUPPL_STATE_NAMES = {
"invalid", "disconn", "disabled", "inactive", "scanning",
"authenticating", "associating", "associated", "4-way-handshake",
@@ -3997,6 +4038,89 @@
}
}
+ private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix,
+ long rawRealtimeMs) {
+ final String allFrequenciesHeader =
+ " All frequencies:\n";
+ final String[] nrFrequencyRangeDescription = new String[]{
+ " Unknown frequency:\n",
+ " Low frequency (less than 1GHz):\n",
+ " Middle frequency (1GHz to 3GHz):\n",
+ " High frequency (3GHz to 6GHz):\n",
+ " Mmwave frequency (greater than 6GHz):\n"};
+ final String signalStrengthHeader =
+ " Signal Strength Time:\n";
+ final String[] signalStrengthDescription = new String[]{
+ " unknown: ",
+ " poor: ",
+ " moderate: ",
+ " good: ",
+ " great: "};
+
+ final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000,
+ STATS_SINCE_CHARGED) / 1000;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("Active Cellular Radio Access Technology Breakdown:");
+ pw.println(sb);
+
+ boolean hasData = false;
+ final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels();
+ for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" ");
+ sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(":\n");
+ sb.append(prefix);
+
+ final int numFreqLvl =
+ rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1;
+ for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) {
+ final int freqDescriptionStart = sb.length();
+ boolean hasFreqData = false;
+ if (rat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ sb.append(nrFrequencyRangeDescription[freqLvl]);
+ } else {
+ sb.append(allFrequenciesHeader);
+ }
+
+ sb.append(prefix);
+ sb.append(signalStrengthHeader);
+ for (int strength = 0; strength < numSignalStrength; strength++) {
+ final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength,
+ rawRealtimeMs);
+ if (timeMs <= 0) continue;
+ hasFreqData = true;
+ sb.append(prefix);
+ sb.append(signalStrengthDescription[strength]);
+ formatTimeMs(sb, timeMs);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+ sb.append(")\n");
+ }
+
+ if (hasFreqData) {
+ hasData = true;
+ pw.print(sb);
+ sb.setLength(0);
+ sb.append(prefix);
+ } else {
+ // No useful data was printed, rewind sb to before the start of this frequency.
+ sb.setLength(freqDescriptionStart);
+ }
+ }
+ }
+
+ if (!hasData) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" (no activity)");
+ pw.println(sb);
+ }
+ }
+
/**
* Temporary for settings.
*/
@@ -5269,6 +5393,8 @@
printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME,
getModemControllerActivity(), which);
+ printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs);
+
pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 2a609b8..071bdea 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -515,6 +515,38 @@
}
/**
+ * Indicates that Bluetooth was toggled on.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) {
+ try {
+ mBatteryStats.noteBluetoothOn(uid, reason, packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates that Bluetooth was toggled off.
+ *
+ * @param uid calling package uid
+ * @param reason why Bluetooth has been turned on
+ * @param packageName package responsible for this change
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) {
+ try {
+ mBatteryStats.noteBluetoothOff(uid, reason, packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicates that a new Bluetooth LE scan has started.
*
* @param ws worksource (to be used for battery blaming).
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..6330661 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);
}
}
@@ -526,12 +526,15 @@
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
- if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
+ boolean warnOnBlocking = mWarnOnBlocking; // Cache it to reduce volatile access.
+
+ if (warnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
&& Binder.sWarnOnBlockingOnCurrentThread.get()) {
// For now, avoid spamming the log by disabling after we've logged
// about this interface at least once
mWarnOnBlocking = false;
+ warnOnBlocking = false;
if (Build.IS_USERDEBUG) {
// Log this as a WTF on userdebug builds.
@@ -578,7 +581,13 @@
}
try {
- return transactNative(code, data, reply, flags);
+ final boolean result = transactNative(code, data, reply, flags);
+
+ if (reply != null && !warnOnBlocking) {
+ reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT);
+ }
+
+ return result;
} finally {
AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 35b9ccc..9970641 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -31,6 +31,7 @@
import android.sysprop.SocProperties;
import android.sysprop.TelephonyProperties;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.View;
@@ -39,6 +40,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -396,6 +398,17 @@
*/
public static final String CODENAME = getString("ro.build.version.codename");
+ /**
+ * All known codenames starting from {@link VERSION_CODES.Q}.
+ *
+ * <p>This includes in development codenames as well.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull public static final Set<String> KNOWN_CODENAMES =
+ new ArraySet<>(new String[]{"Q", "R", "S", "Sv2", "Tiramisu"});
+
private static final String[] ALL_CODENAMES
= getStringList("ro.build.version.all_codenames", ",");
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index aa4b83a..0c3514f 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -165,7 +165,7 @@
private boolean isAngleEnabledByGameMode(Context context, String packageName) {
try {
final boolean gameModeEnabledAngle =
- (mGameManager != null) && mGameManager.getAngleEnabled(packageName);
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
return gameModeEnabledAngle;
} catch (SecurityException e) {
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 425e797..4fe6524 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -21,15 +21,16 @@
import android.os.ParcelDuration;
import android.os.PowerSaveState;
import android.os.WorkSource;
+import android.os.IWakeLockCallback;
/** @hide */
interface IPowerManager
{
void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws,
- String historyTag, int displayId);
+ String historyTag, int displayId, IWakeLockCallback callback);
void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
- int uidtoblame, int displayId);
+ int uidtoblame, int displayId, IWakeLockCallback callback);
@UnsupportedAppUsage
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockUids(IBinder lock, in int[] uids);
@@ -40,6 +41,7 @@
boolean setPowerModeChecked(int mode, boolean enabled);
void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
+ void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback);
boolean isWakeLockLevelSupported(int level);
void userActivity(int displayId, long time, int event, int flags);
@@ -65,6 +67,11 @@
boolean isBatteryDischargePredictionPersonalized();
boolean isDeviceIdleMode();
boolean isLightDeviceIdleMode();
+ boolean isLowPowerStandbySupported();
+ boolean isLowPowerStandbyEnabled();
+ void setLowPowerStandbyEnabled(boolean enabled);
+ void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance);
+ void forceLowPowerStandbyActive(boolean active);
@UnsupportedAppUsage
void reboot(boolean confirm, String reason, boolean wait);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index fe86874..bc7fb78 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 canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne);
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/os/IWakeLockCallback.aidl
similarity index 82%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/os/IWakeLockCallback.aidl
index 861a4ed..89615d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/os/IWakeLockCallback.aidl
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.os;
-parcelable DeviceInfo;
+/**
+ * @hide
+ */
+oneway interface IWakeLockCallback {
+ oneway void onStateChanged(boolean enabled);
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e8b3ae9..9998e12 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -53,6 +54,8 @@
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -229,6 +232,25 @@
private RuntimeException mStack;
+ /** @hide */
+ @TestApi
+ public static final int FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT = 1 << 0;
+
+ /** @hide */
+ @TestApi
+ public static final int FLAG_PROPAGATE_ALLOW_BLOCKING = 1 << 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT,
+ FLAG_PROPAGATE_ALLOW_BLOCKING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ParcelFlags {}
+
+ @ParcelFlags
+ private int mFlags;
+
/**
* Whether or not to parcel the stack trace of an exception. This has a performance
* impact, so should only be included in specific processes and only on debug builds.
@@ -585,6 +607,40 @@
nativeMarkForBinder(mNativePtr, binder);
}
+ /** @hide */
+ @ParcelFlags
+ @TestApi
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** @hide */
+ public void setFlags(@ParcelFlags int flags) {
+ mFlags = flags;
+ }
+
+ /** @hide */
+ public void addFlags(@ParcelFlags int flags) {
+ mFlags |= flags;
+ }
+
+ /** @hide */
+ private boolean hasFlags(@ParcelFlags int flags) {
+ return (mFlags & flags) == flags;
+ }
+
+ /**
+ * This method is used by the AIDL compiler for system components. Not intended to be
+ * used by non-system apps.
+ */
+ // Note: Ideally this method should be @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES),
+ // but we need to make this method public due to the way the aidl compiler is compiled.
+ // We don't really need to protect it; even if 3p / non-system apps, nothing would happen.
+ // This would only work when used on a reply parcel by a binder object that's allowed-blocking.
+ public void setPropagateAllowBlocking() {
+ addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING);
+ }
+
/**
* Returns the total amount of data contained in the parcel.
*/
@@ -2088,6 +2144,102 @@
}
/**
+ * Flatten a homogeneous multi-dimensional array with fixed-size. This delegates to other
+ * APIs to write a one-dimensional array. Use {@link #readFixedArray(Object)} or
+ * {@link #createFixedArray(Class, int[])} with the same dimensions to unmarshal.
+ *
+ * @param val The array to be written.
+ * @param parcelableFlags Contextual flags as per
+ * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
+ * Used only if val is an array of Parcelable objects.
+ * @param dimensions an array of int representing length of each dimension. The array should be
+ * sized with the exact size of dimensions.
+ *
+ * @see #readFixedArray
+ * @see #createFixedArray
+ * @see #writeBooleanArray
+ * @see #writeByteArray
+ * @see #writeCharArray
+ * @see #writeIntArray
+ * @see #writeLongArray
+ * @see #writeFloatArray
+ * @see #writeDoubleArray
+ * @see #writeBinderArray
+ * @see #writeInterfaceArray
+ * @see #writeTypedArray
+ * @throws BadParcelableException If the array's component type is not supported or if its
+ * size doesn't match with the given dimensions.
+ */
+ public <T> void writeFixedArray(@Nullable T val, int parcelableFlags,
+ @NonNull int... dimensions) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+ writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions);
+ }
+
+ private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index,
+ int[] dimensions) {
+ if (index >= dimensions.length) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + dimensions.length);
+ }
+
+ int length = dimensions[index];
+
+ // val should be an array of length N
+ if (val == null) {
+ throw new BadParcelableException("Non-null array shouldn't have a null array.");
+ }
+ if (!val.getClass().isArray()) {
+ throw new BadParcelableException("Not an array: " + val);
+ }
+ if (Array.getLength(val) != length) {
+ throw new BadParcelableException("bad length: expected " + length + ", but got "
+ + Array.getLength(val));
+ }
+
+ // Delegates to other writers if this is a one-dimensional array.
+ // Otherwise, write component arrays with recursive calls.
+
+ final Class<?> componentType = val.getClass().getComponentType();
+ if (!componentType.isArray() && index + 1 != dimensions.length) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + dimensions.length);
+ }
+ if (componentType == boolean.class) {
+ writeBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ writeByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ writeCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ writeIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ writeLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ writeFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ writeDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ writeBinderArray((IBinder[]) val);
+ } else if (IInterface.class.isAssignableFrom(componentType)) {
+ writeInterfaceArray((IInterface[]) val);
+ } else if (Parcelable.class.isAssignableFrom(componentType)) {
+ writeTypedArray((Parcelable[]) val, parcelableFlags);
+ } else if (componentType.isArray()) {
+ writeInt(length);
+ for (int i = 0; i < length; i++) {
+ writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1,
+ dimensions);
+ }
+ } else {
+ throw new BadParcelableException("unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
* Flatten a generic object in to a parcel. The given Object value may
* currently be one of the following types:
*
@@ -2949,7 +3101,15 @@
* Read an object from the parcel at the current dataPosition().
*/
public final IBinder readStrongBinder() {
- return nativeReadStrongBinder(mNativePtr);
+ final IBinder result = nativeReadStrongBinder(mNativePtr);
+
+ // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking
+ // from the object that returned it.
+ if (result != null && hasFlags(
+ FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT | FLAG_PROPAGATE_ALLOW_BLOCKING)) {
+ Binder.allowBlocking(result);
+ }
+ return result;
}
/**
@@ -3711,10 +3871,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);
@@ -3788,6 +3948,317 @@
}
/**
+ * Read a new multi-dimensional array from a parcel. If you want to read Parcelable or
+ * IInterface values, use {@link #readFixedArray(Object, Parcelable.Creator)} or
+ * {@link #readFixedArray(Object, Function)}.
+ * @param val the destination array to hold the read values.
+ *
+ * @see #writeTypedArray
+ * @see #readBooleanArray
+ * @see #readByteArray
+ * @see #readCharArray
+ * @see #readIntArray
+ * @see #readLongArray
+ * @see #readFloatArray
+ * @see #readDoubleArray
+ * @see #readBinderArray
+ * @see #readInterfaceArray
+ * @see #readTypedArray
+ */
+ public <T> void readFixedArray(@NonNull T val) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (componentType == boolean.class) {
+ readBooleanArray((boolean[]) val);
+ } else if (componentType == byte.class) {
+ readByteArray((byte[]) val);
+ } else if (componentType == char.class) {
+ readCharArray((char[]) val);
+ } else if (componentType == int.class) {
+ readIntArray((int[]) val);
+ } else if (componentType == long.class) {
+ readLongArray((long[]) val);
+ } else if (componentType == float.class) {
+ readFloatArray((float[]) val);
+ } else if (componentType == double.class) {
+ readDoubleArray((double[]) val);
+ } else if (componentType == IBinder.class) {
+ readBinderArray((IBinder[]) val);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed interfaces from a parcel.
+ * If you want to read Parcelable values, use
+ * {@link #readFixedArray(Object, Parcelable.Creator)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends IInterface> void readFixedArray(@NonNull T val,
+ @NonNull Function<IBinder, S> asInterface) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ readInterfaceArray((S[]) val, asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ /**
+ * Read a new multi-dimensional array of typed parcelables from a parcel.
+ * If you want to read IInterface values, use
+ * {@link #readFixedArray(Object, Function)}. For values of other types, use
+ * {@link #readFixedArray(Object)}.
+ * @param val the destination array to hold the read values.
+ */
+ public <T, S extends Parcelable> void readFixedArray(@NonNull T val,
+ @NonNull Parcelable.Creator<S> c) {
+ Class<?> componentType = val.getClass().getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ readTypedArray((S[]) val, c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length != Array.getLength(val)) {
+ throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
+ + ", but got " + length);
+ }
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+ }
+
+ private void ensureClassHasExpectedDimensions(@NonNull Class<?> cls, int numDimension) {
+ if (numDimension <= 0) {
+ throw new BadParcelableException("Fixed-size array should have dimensions.");
+ }
+
+ for (int i = 0; i < numDimension; i++) {
+ if (!cls.isArray()) {
+ throw new BadParcelableException("Array has fewer dimensions than expected: "
+ + numDimension);
+ }
+ cls = cls.getComponentType();
+ }
+ if (cls.isArray()) {
+ throw new BadParcelableException("Array has more dimensions than expected: "
+ + numDimension);
+ }
+ }
+
+ /**
+ * Read and return a new multi-dimensional array from a parcel. Returns null if the
+ * previously written array object is null. If you want to read Parcelable or
+ * IInterface values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])} or
+ * {@link #createFixedArray(Class, Function, int[])}.
+ * @param cls the Class object for the target array type. (e.g. int[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ *
+ * @see #writeTypedArray
+ * @see #createBooleanArray
+ * @see #createByteArray
+ * @see #createCharArray
+ * @see #createIntArray
+ * @see #createLongArray
+ * @see #createFloatArray
+ * @see #createDoubleArray
+ * @see #createBinderArray
+ * @see #createInterfaceArray
+ * @see #createTypedArray
+ */
+ @Nullable
+ public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (componentType == boolean.class) {
+ val = (T) createBooleanArray();
+ } else if (componentType == byte.class) {
+ val = (T) createByteArray();
+ } else if (componentType == char.class) {
+ val = (T) createCharArray();
+ } else if (componentType == int.class) {
+ val = (T) createIntArray();
+ } else if (componentType == long.class) {
+ val = (T) createLongArray();
+ } else if (componentType == float.class) {
+ val = (T) createFloatArray();
+ } else if (componentType == double.class) {
+ val = (T) createDoubleArray();
+ } else if (componentType == IBinder.class) {
+ val = (T) createBinderArray();
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i));
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed interfaces from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * Parcelable values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. IFoo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (IInterface.class.isAssignableFrom(componentType)) {
+ val = (T) createInterfaceArray(n -> (S[]) Array.newInstance(componentType, n),
+ asInterface);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), asInterface);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
+ * Read and return a new multi-dimensional array of typed parcelables from a parcel.
+ * Returns null if the previously written array object is null. If you want to read
+ * IInterface values, use {@link #createFixedArray(Class, Function, int[])}.
+ * For values of other types use {@link #createFixedArray(Class, int[])}.
+ * @param cls the Class object for the target array type. (e.g. Foo[][].class)
+ * @param dimensions an array of int representing length of each dimension.
+ */
+ @Nullable
+ public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls,
+ @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
+ // Check if type matches with dimensions
+ // If type is one-dimensional array, delegate to other creators
+ // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
+
+ ensureClassHasExpectedDimensions(cls, dimensions.length);
+
+ T val = null;
+ final Class<?> componentType = cls.getComponentType();
+ if (Parcelable.class.isAssignableFrom(componentType)) {
+ val = (T) createTypedArray(c);
+ } else if (componentType.isArray()) {
+ int length = readInt();
+ if (length < 0) {
+ return null;
+ }
+ if (length != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0]
+ + ", but got " + length);
+ }
+
+ // Create a multi-dimensional array with an innermost component type and dimensions
+ Class<?> innermost = componentType.getComponentType();
+ while (innermost.isArray()) {
+ innermost = innermost.getComponentType();
+ }
+ val = (T) Array.newInstance(innermost, dimensions);
+ for (int i = 0; i < length; i++) {
+ readFixedArray(Array.get(val, i), c);
+ }
+ return val;
+ } else {
+ throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
+ }
+
+ // Check if val is null (which is OK) or has the expected size.
+ // This check doesn't have to be multi-dimensional because multi-dimensional arrays
+ // are created with expected dimensions.
+ if (val != null && Array.getLength(val) != dimensions[0]) {
+ throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
+ + Array.getLength(val));
+ }
+ return val;
+ }
+
+ /**
* Write a heterogeneous array of Parcelable objects into the Parcel.
* Each object in the array is written along with its class name, so
* that the correct class can later be instantiated. As a result, this
@@ -4217,8 +4688,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 +4698,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 +4934,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 +4944,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
@@ -4591,6 +5059,7 @@
}
private void freeBuffer() {
+ mFlags = 0;
resetSqaushingState();
if (mOwnsNativeParcelObject) {
nativeFreeBuffer(mNativePtr);
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..315eef7 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 {
@@ -228,6 +216,17 @@
public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;
/**
+ * Wake lock flag: This wake lock should be held by the system.
+ *
+ * <p>Meant to allow tests to keep the device awake even when power restrictions are active.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public static final int SYSTEM_WAKELOCK = 0x80000000;
+
+ /**
* Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
* {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
* indicates that an object is not in close proximity.
@@ -1196,6 +1195,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.
@@ -2153,6 +2157,105 @@
}
/**
+ * Returns true if Low Power Standby is supported on this device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public boolean isLowPowerStandbySupported() {
+ try {
+ return mService.isLowPowerStandbySupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns true if Low Power Standby is enabled.
+ *
+ * <p>When Low Power Standby is enabled, apps (including apps running foreground services) are
+ * subject to additional restrictions while the device is non-interactive, outside of device
+ * idle maintenance windows: Their network access is disabled, and any wakelocks they hold are
+ * ignored.
+ *
+ * <p>When Low Power Standby is enabled or disabled, a Intent with action
+ * {@link #ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED} is broadcast to registered receivers.
+ */
+ public boolean isLowPowerStandbyEnabled() {
+ try {
+ return mService.isLowPowerStandbyEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby is enabled.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyEnabled(boolean enabled) {
+ try {
+ mService.setLowPowerStandbyEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether Low Power Standby should be active during doze maintenance mode.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @see #isLowPowerStandbyEnabled()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ try {
+ mService.setLowPowerStandbyActiveDuringMaintenance(activeDuringMaintenance);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Force Low Power Standby restrictions to be active.
+ * Does nothing if Low Power Standby is not supported.
+ *
+ * @see #isLowPowerStandbySupported()
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void forceLowPowerStandbyActive(boolean active) {
+ try {
+ mService.forceLowPowerStandbyActive(active);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return whether the given application package name is on the device's power allowlist.
* Apps can be placed on the allowlist through the settings UI invoked by
* {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2638,6 +2741,16 @@
= "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED";
/**
+ * Intent that is broadcast when Low Power Standby is enabled or disabled.
+ * This broadcast is only sent to registered receivers.
+ *
+ * @see #isLowPowerStandbyEnabled()
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED =
+ "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
+
+ /**
* Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
* @hide
*/
@@ -2657,6 +2770,23 @@
public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2;
/**
+ * A listener interface to get notified when the wakelock is enabled/disabled.
+ */
+ public interface WakeLockStateListener {
+ /**
+ * Frameworks could disable the wakelock because either device's power allowlist has
+ * changed, or the app's wakelock has exceeded its quota, or the app goes into cached
+ * state.
+ * <p>
+ * This callback is called whenever the wakelock's state has changed.
+ * </p>
+ *
+ * @param enabled true is enabled, false is disabled.
+ */
+ void onStateChanged(boolean enabled);
+ }
+
+ /**
* A wake lock is a mechanism to indicate that your application needs
* to have the device stay on.
* <p>
@@ -2687,6 +2817,8 @@
private String mHistoryTag;
private final String mTraceName;
private final int mDisplayId;
+ private WakeLockStateListener mListener;
+ private IWakeLockCallback mCallback;
private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT);
@@ -2777,7 +2909,7 @@
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
try {
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
- mHistoryTag, mDisplayId);
+ mHistoryTag, mDisplayId, mCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2970,6 +3102,45 @@
}
};
}
+
+ /**
+ * Set the listener to get notified when the wakelock is enabled/disabled.
+ *
+ * @param executor {@link Executor} to handle listener callback.
+ * @param listener listener to be added, set the listener to null to cancel a listener.
+ */
+ public void setStateListener(@NonNull @CallbackExecutor Executor executor,
+ @Nullable WakeLockStateListener listener) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ synchronized (mToken) {
+ if (listener != mListener) {
+ mListener = listener;
+ if (listener != null) {
+ mCallback = new IWakeLockCallback.Stub() {
+ public void onStateChanged(boolean enabled) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ listener.onStateChanged(enabled);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+ } else {
+ mCallback = null;
+ }
+ if (mHeld) {
+ try {
+ mService.updateWakeLockCallback(mToken, mCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
}
/**
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index eb18b96..ec4d3b6 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -184,6 +184,21 @@
public abstract void setDeviceIdleTempWhitelist(int[] appids);
+ /**
+ * Updates the Low Power Standby allowlist.
+ *
+ * @param uids UIDs that are exempt from Low Power Standby restrictions
+ */
+ public abstract void setLowPowerStandbyAllowlist(int[] uids);
+
+ /**
+ * Used by LowPowerStandbyController to notify the power manager that Low Power Standby's
+ * active state has changed.
+ *
+ * @param active {@code true} to activate Low Power Standby, {@code false} to turn it off.
+ */
+ public abstract void setLowPowerStandbyActive(boolean active);
+
public abstract void startUidChanges();
public abstract void finishUidChanges();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bd65a41..2bd1dbb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4029,6 +4029,53 @@
}
/**
+ * 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}.
+ * @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) {
+ 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);
+ } 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 +4085,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 +4105,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 +4953,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/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 8bc219b..d223a19 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -37,6 +37,7 @@
USAGE_CLASS_UNKNOWN,
USAGE_CLASS_ALARM,
USAGE_CLASS_FEEDBACK,
+ USAGE_CLASS_MEDIA,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UsageClass {}
@@ -105,20 +106,28 @@
public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM;
/**
* Usage value to use for vibrations which mean a request to enter/end a
- * communication, such as a VoIP communication or video-conference.
+ * communication with the user, such as a voice prompt.
*/
public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM;
/**
* Usage value to use for touch vibrations.
+ *
+ * <p>Most typical haptic feedback should be classed as <em>touch</em> feedback. Examples
+ * include vibrations for tap, long press, drag and scroll.
*/
public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK;
/**
- * Usage value to use for vibrations which emulate physical effects, such as edge squeeze.
+ * Usage value to use for vibrations which emulate physical hardware reactions,
+ * such as edge squeeze.
+ *
+ * <p>Note that normal screen-touch feedback "click" effects would typically be
+ * classed as {@link #USAGE_TOUCH}, and that on-screen "physical" animations
+ * like bouncing would be {@link #USAGE_MEDIA}.
*/
public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK;
/**
- * Usage value to use for vibrations which provide a feedback for hardware interaction,
- * such as a fingerprint sensor.
+ * Usage value to use for vibrations which provide a feedback for hardware
+ * component interaction, such as a fingerprint sensor.
*/
public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
/**
@@ -182,7 +191,6 @@
/**
* Return the vibration usage class.
- * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
*/
@UsageClass
public int getUsageClass() {
@@ -191,7 +199,6 @@
/**
* Return the vibration usage.
- * @return one of the values that can be set in {@link Builder#setUsage(int)}
*/
@Usage
public int getUsage() {
@@ -428,16 +435,8 @@
}
/**
- * Sets the attribute describing the type of corresponding vibration.
- * @param usage one of {@link VibrationAttributes#USAGE_ALARM},
- * {@link VibrationAttributes#USAGE_RINGTONE},
- * {@link VibrationAttributes#USAGE_NOTIFICATION},
- * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST},
- * {@link VibrationAttributes#USAGE_TOUCH},
- * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION},
- * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}.
- * {@link VibrationAttributes#USAGE_ACCESSIBILITY}.
- * {@link VibrationAttributes#USAGE_MEDIA}.
+ * Sets the attribute describing the type of the corresponding vibration.
+ * @param usage The type of usage for the vibration
* @return the same Builder instance.
*/
public @NonNull Builder setUsage(@Usage int usage) {
@@ -459,4 +458,3 @@
}
}
}
-
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f490587..21c6487 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -194,27 +194,27 @@
}
/**
- * Create a waveform vibration.
+ * Create a waveform vibration, using only off/on transitions at the provided time intervals,
+ * and potentially repeating.
*
- * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
- * each pair, the value in the amplitude array determines the strength of the vibration and the
- * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
- * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning
+ * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then
+ * the number of milliseconds turned off, and so on. Consequently, the first timing value will
+ * often be 0, so that the effect will start vibrating immediately.
*
- * <p>The amplitude array of the generated waveform will be the same size as the given
- * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
- * starting with 0. Therefore the first timing value will be the period to wait before turning
- * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
- * strength, etc.
+ * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with
+ * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE},
+ * beginning with 0.
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
* and should be cancelled via {@link Vibrator#cancel()}.
*
- * @param timings The pattern of alternating on-off timings, starting with off. Timing values
- * of 0 will cause the timing / amplitude pair to be ignored.
- * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
- * want to repeat.
+ * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and
+ * representing the length of time to sustain the individual item (not
+ * cumulative).
+ * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+ * want to repeat indefinitely.
*
* @return The desired effect.
*/
@@ -229,11 +229,10 @@
/**
* Create a waveform vibration.
*
- * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
- * each pair, the value in the amplitude array determines the strength of the vibration and the
- * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
- * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
- * pairs with a timing value of 0 will be ignored.
+ * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
+ * provided in separate arrays. For each pair, the value in the amplitude array determines
+ * the strength of the vibration and the value in the timing array determines how long it
+ * vibrates for, in milliseconds.
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
@@ -244,8 +243,8 @@
* @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
* must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
* amplitude value of 0 implies the motor is off.
- * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
- * want to repeat.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you don't
+ * want to repeat indefinitely.
*
* @return The desired effect.
*/
@@ -411,9 +410,9 @@
*
* <p>The waveform will start the first transition from the vibrator off state, with the
* resonant frequency by default. To provide an initial state, use
- * {@link #startWaveform(VibrationParameter)}.
+ * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
*
- * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform() {
@@ -422,14 +421,16 @@
/**
* Start building a waveform vibration with an initial state specified by a
- * {@link VibrationParameter}.
+ * {@link VibrationEffect.VibrationParameter}.
*
* <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
* control over vibration amplitude and frequency via smooth transitions between values.
*
- * @param initialParameter The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration.
+ * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
@@ -440,17 +441,19 @@
/**
* Start building a waveform vibration with an initial state specified by two
- * {@link VibrationParameter VibrationParameters}.
+ * {@link VibrationEffect.VibrationParameter VibrationParameters}.
*
* <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
* control over vibration amplitude and frequency via smooth transitions between values.
*
- * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration.
- * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the
- * beginning of the vibration, must be a different type of parameter
- * than the one specified by the first argument.
+ * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration.
+ * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
+ * applied at the beginning of the vibration, must be a different type
+ * of parameter than the one specified by the first argument.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
+ *
+ * @see VibrationEffect.WaveformBuilder
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
@@ -806,7 +809,46 @@
}
/**
- * A composition of haptic primitives that, when combined, create a single haptic effect.
+ * A composition of haptic elements that are combined to be playable as a single
+ * {@link VibrationEffect}.
+ *
+ * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
+ * can be added to a composition to create a custom vibration effect. Here is an example of an
+ * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
+ * and an extra tick 100ms after:
+ *
+ * <code>
+ * VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+ * .compose();
+ * </code>
+ *
+ * <p>Composition elements can also be {@link VibrationEffect} instances, including other
+ * compositions, and off durations, which are periods of time when the vibrator will be
+ * turned off. Here is an example of a composition that "warms up" with a light tap,
+ * a stronger double tap, then repeats a vibration pattern indefinitely:
+ *
+ * <code>
+ * VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ * .addOffDuration(Duration.ofMillis(10))
+ * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ * .addOffDuration(Duration.ofMillis(50))
+ * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex))
+ * .compose();
+ * </code>
+ *
+ * <p>When choosing to play a composed effect, you should check that individual components are
+ * supported by the device by using the appropriate vibrator method:
+ *
+ * <ul>
+ * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}.
+ * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}.
+ * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked
+ * using {@link Vibrator#hasAmplitudeControl}.
+ * </ul>
*
* @see VibrationEffect#startComposition()
*/
@@ -1092,16 +1134,77 @@
* A builder for waveform haptic effects.
*
* <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
- * parameters. These parameters can be the vibration amplitude or frequency, for example.
+ * parameters. These parameters can be the vibration amplitude, frequency, or both.
+ *
+ * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
+ * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
+ *
+ * <code>
+ * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+ * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
+ *
+ * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ * .addSustain(Duration.ofMillis(200))
+ * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ * .build();
+ * </code>
+ *
+ * <p>The initial state of the waveform can be set via
+ * {@link VibrationEffect#startWaveform(VibrationParameter)} or
+ * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
+ * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
+ * represented by zero amplitude, at the vibrator's resonant frequency.
+ *
+ * <p>Repeating waveforms can be created by building the repeating block separately and adding
+ * it to the end of a composition with
+ * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
*
* <p>Note that physical vibration actuators have different reaction times for changing
* amplitude and frequency. Durations specified here represent a timeline for the target
* parameters, and quality of effects may be improved if the durations allow time for a
* transition to be smoothly applied.
*
- * <p>Repeating waveforms can be built by constructing the repeating block separately and adding
- * it to the end of a composition using
- * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}.
+ * <p>The following example illustrates both an initial state and a repeating section, using
+ * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
+ * repeated beating effect with a rise that stretches out and a sharp finish.
+ *
+ * <code>
+ * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(30))
+ * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(50))
+ * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+ * .build();
+ *
+ * VibrationEffect effect = VibrationEffect.startComposition()
+ * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ * .addOffDuration(Duration.ofMillis(20))
+ * .repeatEffectIndefinitely(patternToBeRepeated)
+ * .compose();
+ * </code>
+ *
+ * <p>The amplitude step waveforms that can be created via
+ * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
+ * {@link WaveformBuilder} by adding zero duration transitions:
+ *
+ * <code>
+ * // These two effects are the same
+ * VibrationEffect waveform = VibrationEffect.createWaveform(
+ * new long[] { 10, 20, 30 }, // timings in milliseconds
+ * new int[] { 51, 102, 204 }, // amplitudes in [0,255]
+ * -1); // repeat index
+ *
+ * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * .addSustain(Duration.ofMillis(10))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+ * .addSustain(Duration.ofMillis(20))
+ * .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+ * .addSustain(Duration.ofMillis(30))
+ * .build();
+ * </code>
*
* @see VibrationEffect#startWaveform
*/
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 23baa5d..78f1cb1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -98,7 +98,8 @@
/**
* Vibration effect support: unsupported
*
- * This effect is <b>not</b> supported by the underlying hardware.
+ * This effect is <b>not</b> natively supported by the underlying hardware, although
+ * the system may still play a fallback vibration.
*/
public static final int VIBRATION_EFFECT_SUPPORT_NO = 2;
@@ -485,20 +486,25 @@
String reason, @NonNull VibrationAttributes attributes);
/**
- * Query whether the vibrator supports the given effects.
+ * Query whether the vibrator natively supports the given effects.
*
- * Not all hardware reports its effect capabilities, so the system may not necessarily know
- * whether an effect is supported or not.
+ * <p>If an effect is not supported, the system may still automatically fall back to playing
+ * a simpler vibration instead, which is not optimised for the specific device. This includes
+ * the unknown case, which can't be determined in advance, that will dynamically attempt to
+ * fall back if the optimised effect fails to play.
*
- * The returned array will be the same length as the query array and the value at a given index
- * will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index in the
- * querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or
- * {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's
- * supported or not.
+ * <p>The returned array will be the same length as the query array and the value at a given
+ * index will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index
+ * in the querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't
+ * supported, or {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether
+ * it's supported or not, as some hardware doesn't report its effect capabilities.
+ *
+ * <p>Use {@link #areAllEffectsSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one effect.
*
* @param effectIds Which effects to query for.
* @return An array containing the systems current knowledge about whether the given effects
- * are supported or not.
+ * are natively supported by the device, or not.
*/
@NonNull
@VibrationEffectSupport
@@ -515,39 +521,47 @@
/**
* Query whether the vibrator supports all of the given effects.
*
- * Not all hardware reports its effect capabilities, so the system may not necessarily know
- * whether an effect is supported or not.
+ * <p>If an effect is not supported, the system may still automatically fall back to a simpler
+ * vibration instead, which is not optimised for the specific device, however vibration isn't
+ * guaranteed in this case.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are
* supported by the hardware.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
- * query is not supported.
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the
+ * query is not supported, and using them may fall back to an un-optimized vibration or no
+ * vibration.
*
- * If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know whether
- * all of the effects are supported. It may support any or all of the queried effects,
+ * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know
+ * whether all of the effects are supported. It may support any or all of the queried effects,
* but there's no way to programmatically know whether a {@link #vibrate} call will successfully
* cause a vibration. It's guaranteed, however, that none of the queried effects are
* definitively unsupported by the hardware.
*
+ * <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect.
+ *
* @param effectIds Which effects to query for.
- * @return Whether all of the effects are supported.
+ * @return Whether all of the effects are natively supported by the device.
*/
@VibrationEffectSupport
public final int areAllEffectsSupported(
@NonNull @VibrationEffect.EffectType int... effectIds) {
- int support = VIBRATION_EFFECT_SUPPORT_YES;
- for (int supported : areEffectsSupported(effectIds)) {
- if (supported == VIBRATION_EFFECT_SUPPORT_NO) {
- return VIBRATION_EFFECT_SUPPORT_NO;
- } else if (supported == VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
- support = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ VibratorInfo info = getInfo();
+ int allSupported = VIBRATION_EFFECT_SUPPORT_YES;
+ for (int effectId : effectIds) {
+ switch (info.isEffectSupported(effectId)) {
+ case VIBRATION_EFFECT_SUPPORT_NO:
+ return VIBRATION_EFFECT_SUPPORT_NO;
+ case VIBRATION_EFFECT_SUPPORT_YES:
+ continue;
+ default: // VIBRATION_EFFECT_SUPPORT_UNKNOWN
+ allSupported = VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ break;
}
}
- return support;
+ return allSupported;
}
-
/**
* Query whether the vibrator supports the given primitives.
*
@@ -555,6 +569,12 @@
* will contain whether the effect at that same index in the querying array is supported or
* not.
*
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #areAllPrimitivesSupported(int...)} to get a single combined result,
+ * or for convenience when querying exactly one primitive.
+ *
* @param primitiveIds Which primitives to query for.
* @return Whether the primitives are supported.
*/
@@ -572,13 +592,19 @@
/**
* Query whether the vibrator supports all of the given primitives.
*
+ * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if
+ * it is played.
+ *
+ * <p>Use {@link #arePrimitivesSupported(int...)} to get individual results for each primitive.
+ *
* @param primitiveIds Which primitives to query for.
- * @return Whether primitives effects are supported.
+ * @return Whether all specified primitives are supported.
*/
public final boolean areAllPrimitivesSupported(
@NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
- for (boolean supported : arePrimitivesSupported(primitiveIds)) {
- if (!supported) {
+ VibratorInfo info = getInfo();
+ for (int primitiveId : primitiveIds) {
+ if (!info.isPrimitiveSupported(primitiveId)) {
return false;
}
}
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 68b5679..02db274 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -22,5 +22,7 @@
interface ILogcatManagerService {
void startThread(in int uid, in int gid, in int pid, in int fd);
void finishThread(in int uid, in int gid, in int pid, in int fd);
+ void approve(in int uid, in int gid, in int pid, in int fd);
+ void decline(in int uid, in int gid, in int pid, in int fd);
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8df659d..63616da 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -291,6 +291,8 @@
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
/** {@hide} */
public static final int FLAG_INCLUDE_RECENT = 1 << 11;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1328,6 +1330,23 @@
}
/**
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user and the user it shares media with
+ * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
+ * <p>
+ * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
+ * includes the volumes belonging to any user it shares media with
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ public @NonNull List<StorageVolume> getStorageVolumesIncludingSharedProfiles() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE));
+ return res;
+ }
+
+ /**
* Return the list of shared/external storage volumes both currently and
* recently available to the calling user.
* <p>
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 8ee52c2..e1f112a 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,8 +16,6 @@
package android.os.storage;
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -308,11 +306,9 @@
/**
* Returns the user that owns this volume
- *
- * {@hide}
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/193460475) : Android Lint handle API change from systemApi to public Api incorrectly
+ @SuppressLint("NewApi")
public @NonNull UserHandle getOwner() {
return mOwner;
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 1c0320e..619c870 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -79,7 +79,8 @@
void revokeOwnPermissionsOnKill(String packageName, in List<String> permissions);
void startOneTimePermissionSession(String packageName, int userId, long timeout,
- int importanceToResetTimer, int importanceToKeepSessionAlive);
+ long revokeAfterKilledDelay, int importanceToResetTimer,
+ int importanceToKeepSessionAlive);
void stopOneTimePermissionSession(String packageName, int userId);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 0cf06aa..a005ab4e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -907,21 +907,23 @@
* <li>Each permission in {@code permissions} must be a runtime permission.
* </ul>
* <p>
- * For every permission in {@code permissions}, the entire permission group it belongs to will
- * be revoked. This revocation happens asynchronously and kills all processes running in the
- * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+ * Background permissions which have no corresponding foreground permission still granted once
+ * the revocation is effective will also be revoked.
+ * <p>
+ * This revocation happens asynchronously and kills all processes running in the same UID as
+ * {@code packageName}. It will be triggered once it is safe to do so.
*
* @param packageName The name of the package for which the permissions will be revoked.
* @param permissions List of permissions to be revoked.
- * @param callback Callback called when the revocation request has been completed.
*
- * @see Context#revokeOwnPermissionsOnKill(Collection)
+ * @see Context#revokeOwnPermissionsOnKill(java.util.Collection)
*
* @hide
*/
public void revokeOwnPermissionsOnKill(@NonNull String packageName,
- @NonNull List<String> permissions, AndroidFuture<Void> callback) {
+ @NonNull List<String> permissions) {
mRemoteService.postAsync(service -> {
+ AndroidFuture<Void> callback = new AndroidFuture<>();
service.revokeOwnPermissionsOnKill(packageName, permissions, callback);
return callback;
}).whenComplete((result, err) -> {
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8d9f82b..3292e71 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -291,7 +291,7 @@
/**
* Called when a package is considered inactive based on the criteria given by
- * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
+ * {@link PermissionManager#startOneTimePermissionSession(String, long, long, int, int)}.
* This method is called at the end of a one-time permission session
*
* @param packageName The package that has been inactive
@@ -329,9 +329,11 @@
* Triggers the revocation of one or more permissions for a package. This should only be called
* at the request of {@code packageName}.
* <p>
- * For every permission in {@code permissions}, the entire permission group it belongs to will
- * be revoked. This revocation happens asynchronously and kills all processes running in the
- * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+ * Background permissions which have no corresponding foreground permission still granted once
+ * the revocation is effective will also be revoked.
+ * <p>
+ * This revocation happens asynchronously and kills all processes running in the same UID as
+ * {@code packageName}. It will be triggered once it is safe to do so.
*
* @param packageName The name of the package for which the permissions will be revoked.
* @param permissions List of permissions to be revoked.
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 15f13eb..12fa0dd 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -20,6 +20,7 @@
import android.Manifest;
import android.annotation.CheckResult;
+import android.annotation.DurationMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1282,6 +1283,22 @@
}
/**
+ * Starts a one-time permission session for a given package.
+ * @see #startOneTimePermissionSession(String, long, long, int, int)
+ * @hide
+ * @deprecated Use {@link #startOneTimePermissionSession(String, long, long, int, int)} instead
+ */
+ @Deprecated
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
+ public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis,
+ @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
+ @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
+ startOneTimePermissionSession(packageName, timeoutMillis, -1,
+ importanceToResetTimer, importanceToKeepSessionAlive);
+ }
+
+ /**
* Starts a one-time permission session for a given package. A one-time permission session is
* ended if app becomes inactive. Inactivity is defined as the package's uid importance level
* staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
@@ -1301,25 +1318,33 @@
* {@link PermissionControllerService#onOneTimePermissionSessionTimeout(String)} is invoked.
* </p>
* <p>
- * Note that if there is currently an active session for a package a new one isn't created and
- * the existing one isn't changed.
+ * Note that if there is currently an active session for a package a new one isn't created but
+ * each parameter of the existing one will be updated to the more aggressive of both sessions.
+ * This means that durations will be set to the shortest parameter and importances will be set
+ * to the lowest one.
* </p>
* @param packageName The package to start a one-time permission session for
* @param timeoutMillis Number of milliseconds for an app to be in an inactive state
+ * @param revokeAfterKilledDelayMillis Number of milliseconds to wait before revoking on the
+ * event an app is terminated. Set to -1 to use default
+ * value for the device.
* @param importanceToResetTimer The least important level to uid must be to reset the timer
* @param importanceToKeepSessionAlive The least important level the uid must be to keep the
- * session alive
+ * session alive
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
- public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis,
+ public void startOneTimePermissionSession(@NonNull String packageName,
+ @DurationMillisLong long timeoutMillis,
+ @DurationMillisLong long revokeAfterKilledDelayMillis,
@ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
@ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
try {
mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
- timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
+ importanceToKeepSessionAlive);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3f54408..ee6f9c0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2386,6 +2386,15 @@
"android.settings.ENABLE_MMS_DATA_REQUEST";
/**
+ * Shows restrict settings dialog when settings is blocked.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG =
+ "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+
+ /**
* Integer value that specifies the reason triggering enable MMS data notification.
* This must be passed as an extra field to the {@link #ACTION_ENABLE_MMS_DATA_REQUEST}.
* Extra with value of EnableMmsDataReason interface.
@@ -2427,6 +2436,23 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING";
+ /**
+ * Activity Action: Show a screen of bedtime settings, which is provided by the wellbeing app.
+ * <p>
+ * The handler of this intent action may not exist.
+ * <p>
+ * To start an activity with this intent, apps should set the wellbeing package explicitly in
+ * the intent together with this action. The wellbeing package is defined in
+ * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -6068,9 +6094,11 @@
}
/** @hide */
- @UnsupportedAppUsage
- public static String getStringForUser(ContentResolver resolver, String name,
- int userHandle) {
+ @SystemApi
+ @Nullable
+ @SuppressLint("VisiblySynchronized")
+ public static String getStringForUser(@NonNull ContentResolver resolver,
+ @NonNull String name, int userHandle) {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global.");
@@ -6302,8 +6330,9 @@
}
/** @hide */
- @UnsupportedAppUsage
- public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+ @SystemApi
+ public static int getIntForUser(@NonNull ContentResolver cr, @NonNull String name,
+ int def, int userHandle) {
String v = getStringForUser(cr, name, userHandle);
return parseIntSettingWithDefault(v, def);
}
@@ -6574,6 +6603,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_NAME = "bluetooth_name";
/**
@@ -6581,6 +6612,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
/**
@@ -6588,6 +6621,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
/**
@@ -10033,6 +10068,13 @@
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
/**
+ * The duration of timeout, in milliseconds, to switch from a non-primary user to the
+ * primary user when the device is docked.
+ * @hide
+ */
+ public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+
+ /**
* Backup manager behavioral parameters.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -10259,6 +10301,22 @@
public static final String NEARBY_SHARING_COMPONENT = "nearby_sharing_component";
/**
+ * Nearby Sharing Slice URI for the SliceProvider to
+ * read Nearby Sharing scan results and then draw the UI.
+ * @hide
+ */
+ public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
+
+ /**
+ * Current provider of Fast Pair saved devices page.
+ * Default value in @string/config_defaultNearbyFastPairSettingsDevicesComponent.
+ * No VALIDATOR as this setting will not be backed up.
+ * @hide
+ */
+ public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT =
+ "nearby_fast_pair_settings_devices_component";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -10610,6 +10668,15 @@
public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
/**
+ * Setting to store denylisted system languages by the CEC {@code <Set Menu Language>}
+ * confirmation dialog.
+ *
+ * @hide
+ */
+ public static final String HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST =
+ "hdmi_cec_set_menu_language_denylist";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
@@ -10826,6 +10893,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
/**
@@ -10834,6 +10903,8 @@
* {@hide}
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
/**
@@ -12371,6 +12442,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
/**
@@ -12378,6 +12451,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
/**
@@ -12385,6 +12460,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
/**
@@ -12392,6 +12469,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS =
"ble_scan_low_latency_window_ms";
@@ -12400,6 +12479,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS =
"ble_scan_low_power_interval_ms";
@@ -12408,6 +12489,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BALANCED_INTERVAL_MS =
"ble_scan_balanced_interval_ms";
@@ -12416,6 +12499,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS =
"ble_scan_low_latency_interval_ms";
@@ -12424,6 +12509,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
/**
@@ -13156,6 +13243,8 @@
/** {@hide} */
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String
BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
/** {@hide} */
@@ -16706,6 +16795,30 @@
public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode";
/**
+ * Setting indicating whether Low Power Standby is enabled, if supported.
+ *
+ * Values are:
+ * 0: disabled
+ * 1: enabled
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_STANDBY_ENABLED = "low_power_standby_enabled";
+
+ /**
+ * Setting indicating whether Low Power Standby is allowed to be active during doze
+ * maintenance mode.
+ *
+ * Values are:
+ * 0: Low Power Standby will be disabled during doze maintenance mode
+ * 1: Low Power Standby can be active during doze maintenance mode
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE =
+ "low_power_standby_active_during_maintenance";
+
+ /**
* Settings migrated from Wear OS settings provider.
* @hide
*/
@@ -17210,6 +17323,12 @@
*/
public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED =
"clockwork_long_press_to_assistant_enabled";
+
+ /*
+ * Whether the device has Wet Mode/ Touch Lock Mode enabled.
+ * @hide
+ */
+ public static final String WET_MODE_ON = "wet_mode_on";
}
}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 34e35d4..3ff0161 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1425,25 +1425,6 @@
public static final String KEY_TYPE = "key_type";
/**
- * MVNO type:
- * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
- * <P> Type: TEXT </P>
- */
- public static final String MVNO_TYPE = "mvno_type";
-
- /**
- * MVNO data.
- * Use the following examples.
- * <ul>
- * <li>SPN: A MOBILE, BEN NL, ...</li>
- * <li>IMSI: 302720x94, 2060188, ...</li>
- * <li>GID: 4E, 33, ...</li>
- * </ul>
- * <P> Type: TEXT </P>
- */
- public static final String MVNO_MATCH_DATA = "mvno_match_data";
-
- /**
* The carrier public key that is used for the IMSI encryption.
* <P> Type: TEXT </P>
*/
@@ -1470,6 +1451,11 @@
public static final String LAST_MODIFIED = "last_modified";
/**
+ * Carrier ID of the operetor.
+ * <P> Type: TEXT </P>
+ */
+ public static final String CARRIER_ID = "carrier_id";
+ /**
* The {@code content://} style URL for this table.
*/
@NonNull
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
index 49ab5db..f5c59b5 100644
--- a/core/java/android/service/attention/AttentionService.java
+++ b/core/java/android/service/attention/AttentionService.java
@@ -24,11 +24,14 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Slog;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
/**
@@ -51,6 +54,7 @@
*/
@SystemApi
public abstract class AttentionService extends Service {
+ private static final String LOG_TAG = "AttentionService";
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the {@link android.Manifest.permission#BIND_ATTENTION_SERVICE}
@@ -80,6 +84,9 @@
/** Camera permission is not granted. */
public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6;
+ /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+ public static final double PROXIMITY_UNKNOWN = -1;
+
/**
* Result codes for when attention check was successful.
*
@@ -118,6 +125,20 @@
Preconditions.checkNotNull(callback);
AttentionService.this.onCancelAttentionCheck(new AttentionCallback(callback));
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void onStartProximityUpdates(IProximityCallback callback) {
+ Objects.requireNonNull(callback);
+ AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback));
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onStopProximityUpdates() {
+ AttentionService.this.onStopProximityUpdates();
+ }
};
@Nullable
@@ -143,6 +164,23 @@
*/
public abstract void onCancelAttentionCheck(@NonNull AttentionCallback callback);
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is unregistered.
+ *
+ * @param callback the callback to return the result to
+ */
+ public void onStartProximityUpdates(@NonNull ProximityCallback callback) {
+ Slog.w(LOG_TAG, "Override this method.");
+ }
+
+ /**
+ * Requests to stop providing continuous updates until the callback is registered.
+ */
+ public void onStopProximityUpdates() {
+ Slog.w(LOG_TAG, "Override this method.");
+ }
+
/** Callbacks for AttentionService results. */
public static final class AttentionCallback {
@NonNull private final IAttentionCallback mCallback;
@@ -174,4 +212,26 @@
}
}
}
+
+ /** Callbacks for ProximityCallback results. */
+ public static final class ProximityCallback {
+ @NonNull private final WeakReference<IProximityCallback> mCallback;
+
+ private ProximityCallback(@NonNull IProximityCallback callback) {
+ mCallback = new WeakReference<>(callback);
+ }
+
+ /**
+ * @param distance the estimated distance of the user (in meter)
+ * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
+ *
+ */
+ public void onProximityUpdate(double distance) {
+ try {
+ mCallback.get().onProximityUpdate(distance);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
}
diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl
index 99e7997..8bb881b 100644
--- a/core/java/android/service/attention/IAttentionService.aidl
+++ b/core/java/android/service/attention/IAttentionService.aidl
@@ -17,6 +17,7 @@
package android.service.attention;
import android.service.attention.IAttentionCallback;
+import android.service.attention.IProximityCallback;
/**
* Interface for a concrete implementation to provide to the AttentionManagerService.
@@ -26,4 +27,6 @@
oneway interface IAttentionService {
void checkAttention(IAttentionCallback callback);
void cancelAttentionCheck(IAttentionCallback callback);
+ void onStartProximityUpdates(IProximityCallback callback);
+ void onStopProximityUpdates();
}
\ No newline at end of file
diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityCallback.aidl
new file mode 100644
index 0000000..9ecf9bc
--- /dev/null
+++ b/core/java/android/service/attention/IProximityCallback.aidl
@@ -0,0 +1,10 @@
+package android.service.attention;
+
+/**
+ * Callback for onStartProximityUpdates request.
+ *
+ * @hide
+ */
+oneway interface IProximityCallback {
+ void onProximityUpdate(double distance);
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 29c7796..cb1b5d3 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -578,6 +578,14 @@
public static final String SERVICE_META_DATA = "android.autofill";
/**
+ * Name of the {@link FillResponse} extra used to return a delayed fill response.
+ *
+ * <p>Please see {@link FillRequest#getDelayedFillIntentSender()} on how to send a delayed
+ * fill response to framework.</p>
+ */
+ public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
+
+ /**
* Name of the {@link IResultReceiver} extra used to return the primary result of a request.
*
* @hide
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 86341a9..cfb6909 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -65,6 +65,16 @@
* can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
* an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
*
+ * <a name="FillDialogUI"></a>
+ * <h3>Fill Dialog UI</h3>
+ *
+ * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill
+ * suggestions are available when the user clicks on a field that supports filling the dialog UI,
+ * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the
+ * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset.
+ * If the user focuses on the view before suggestions are available, will fall back to dropdown UI
+ * or inline suggestions.
+ *
* <a name="Authentication"></a>
* <h3>Dataset authentication</h3>
*
@@ -92,10 +102,9 @@
* <ol>
* <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
* {@link AutofillValue#isText() text} or is empty, all datasets are shown.
- * <li>Datasets that have a filter regex (set through
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
- * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
- * regex matches the view's text value converted to lower case are shown.
+ * <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)}
+ * and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's
+ * text value converted to lower case are shown.
* <li>Datasets that do not require authentication, have a field value that is
* {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
* with the lower case value of the view's text are shown.
@@ -107,11 +116,13 @@
private final ArrayList<AutofillId> mFieldIds;
private final ArrayList<AutofillValue> mFieldValues;
private final ArrayList<RemoteViews> mFieldPresentations;
+ private final ArrayList<RemoteViews> mFieldDialogPresentations;
private final ArrayList<InlinePresentation> mFieldInlinePresentations;
private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private final ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private final ClipData mFieldContent;
private final RemoteViews mPresentation;
+ private final RemoteViews mDialogPresentation;
@Nullable private final InlinePresentation mInlinePresentation;
@Nullable private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
@@ -121,11 +132,13 @@
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
+ mFieldDialogPresentations = builder.mFieldDialogPresentations;
mFieldInlinePresentations = builder.mFieldInlinePresentations;
mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
mFieldFilters = builder.mFieldFilters;
mFieldContent = builder.mFieldContent;
mPresentation = builder.mPresentation;
+ mDialogPresentation = builder.mDialogPresentation;
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
@@ -153,6 +166,12 @@
}
/** @hide */
+ public RemoteViews getFieldDialogPresentation(int index) {
+ final RemoteViews customPresentation = mFieldDialogPresentations.get(index);
+ return customPresentation != null ? customPresentation : mDialogPresentation;
+ }
+
+ /** @hide */
public @Nullable InlinePresentation getFieldInlinePresentation(int index) {
final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
@@ -219,6 +238,9 @@
if (mFieldPresentations != null) {
builder.append(", fieldPresentations=").append(mFieldPresentations.size());
}
+ if (mFieldDialogPresentations != null) {
+ builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size());
+ }
if (mFieldInlinePresentations != null) {
builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
}
@@ -232,6 +254,9 @@
if (mPresentation != null) {
builder.append(", hasPresentation");
}
+ if (mDialogPresentation != null) {
+ builder.append(", hasDialogPresentation");
+ }
if (mInlinePresentation != null) {
builder.append(", hasInlinePresentation");
}
@@ -264,11 +289,13 @@
private ArrayList<AutofillId> mFieldIds;
private ArrayList<AutofillValue> mFieldValues;
private ArrayList<RemoteViews> mFieldPresentations;
+ private ArrayList<RemoteViews> mFieldDialogPresentations;
private ArrayList<InlinePresentation> mFieldInlinePresentations;
private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private ClipData mFieldContent;
private RemoteViews mPresentation;
+ private RemoteViews mDialogPresentation;
@Nullable private InlinePresentation mInlinePresentation;
@Nullable private InlinePresentation mInlineTooltipPresentation;
private IntentSender mAuthentication;
@@ -279,7 +306,9 @@
* Creates a new builder.
*
* @param presentation The presentation used to visualize this dataset.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public Builder(@NonNull RemoteViews presentation) {
Objects.requireNonNull(presentation, "presentation must be non-null");
mPresentation = presentation;
@@ -294,19 +323,34 @@
* as inline suggestions. If the dataset supports inline suggestions,
* this should not be null.
* @hide
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
@SystemApi
+ @Deprecated
public Builder(@NonNull InlinePresentation inlinePresentation) {
Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null");
mInlinePresentation = inlinePresentation;
}
/**
+ * Creates a new builder.
+ *
+ * @param presentations The presentations used to visualize this dataset.
+ */
+ public Builder(@NonNull Presentations presentations) {
+ Objects.requireNonNull(presentations, "presentations must be non-null");
+
+ mPresentation = presentations.getMenuPresentation();
+ mInlinePresentation = presentations.getInlinePresentation();
+ mInlineTooltipPresentation = presentations.getInlineTooltipPresentation();
+ mDialogPresentation = presentations.getDialogPresentation();
+ }
+
+ /**
* Creates a new builder for a dataset where each field will be visualized independently.
*
- * <p>When using this constructor, fields must be set through
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
- * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
+ * <p>When using this constructor, a presentation must be provided for each field through
+ * {@link #setField(AutofillId, Field)}.
*/
public Builder() {
}
@@ -318,7 +362,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public @NonNull Builder setInlinePresentation(
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
@@ -339,7 +385,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #Builder(Presentations)} instead.
*/
+ @Deprecated
public @NonNull Builder setInlinePresentation(
@NonNull InlinePresentation inlinePresentation,
@NonNull InlinePresentation inlineTooltipPresentation) {
@@ -479,7 +527,7 @@
"Content items cannot contain an Intent: content=" + content);
}
}
- setLifeTheUniverseAndEverything(id, null, null, null, null);
+ setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
mFieldContent = content;
return this;
}
@@ -509,10 +557,12 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- setLifeTheUniverseAndEverything(id, value, null, null, null);
+ setLifeTheUniverseAndEverything(id, value, null, null, null, null, null);
return this;
}
@@ -537,12 +587,14 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, null, null);
+ setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null);
return this;
}
@@ -572,13 +624,16 @@
* @return this builder.
* @throws IllegalStateException if the builder was constructed without a
* {@link RemoteViews presentation} or {@link #build()} was already called.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter) {
throwIfDestroyed();
Preconditions.checkState(mPresentation != null,
"Dataset presentation not set on constructor");
- setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(
+ id, value, null, null, null, new DatasetFieldFilter(filter), null);
return this;
}
@@ -610,13 +665,15 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, null,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, presentation, null, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
@@ -641,13 +698,16 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null);
+ setLifeTheUniverseAndEverything(
+ id, value, presentation, inlinePresentation, null, null, null);
return this;
}
@@ -672,7 +732,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation,
@NonNull InlinePresentation inlineTooltipPresentation) {
@@ -682,7 +744,7 @@
Objects.requireNonNull(inlineTooltipPresentation,
"inlineTooltipPresentation cannot be null");
setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- inlineTooltipPresentation, null);
+ inlineTooltipPresentation, null, null);
return this;
}
@@ -718,15 +780,17 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation,
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(presentation, "presentation cannot be null");
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
@@ -756,7 +820,9 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
*/
+ @Deprecated
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@Nullable Pattern filter, @NonNull RemoteViews presentation,
@NonNull InlinePresentation inlinePresentation,
@@ -767,7 +833,91 @@
Objects.requireNonNull(inlineTooltipPresentation,
"inlineTooltipPresentation cannot be null");
setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
- inlineTooltipPresentation, new DatasetFieldFilter(filter));
+ inlineTooltipPresentation, new DatasetFieldFilter(filter), null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field.
+ *
+ * Before Android 13, this information could be provided using several overloaded
+ * setValue(...) methods. This method replaces those with a Builder pattern.
+ * For example, in the old workflow, the app sets a field would be:
+ * <pre class="prettyprint">
+ * Dataset.Builder dataset = new Dataset.Builder();
+ * if (filter != null) {
+ * if (presentation != null) {
+ * if (inlinePresentation != null) {
+ * dataset.setValue(id, value, filter, presentation, inlinePresentation)
+ * } else {
+ * dataset.setValue(id, value, filter, presentation);
+ * }
+ * } else {
+ * dataset.setValue(id, value, filter);
+ * }
+ * } else {
+ * if (presentation != null) {
+ * if (inlinePresentation != null) {
+ * dataset.setValue(id, value, presentation, inlinePresentation)
+ * } else {
+ * dataset.setValue(id, value, presentation);
+ * }
+ * } else {
+ * dataset.setValue(id, value);
+ * }
+ * }
+ * </pre>
+ * <p>The new workflow would be:
+ * <pre class="prettyprint">
+ * Field.Builder fieldBuilder = new Field.Builder();
+ * if (value != null) {
+ * fieldBuilder.setValue(value);
+ * }
+ * if (filter != null) {
+ * fieldBuilder.setFilter(filter);
+ * }
+ * Presentations.Builder presentationsBuilder = new Presentations.Builder(id);
+ * if (presentation != null) {
+ * presentationsBuilder.setMenuPresentation(presentation);
+ * }
+ * if (inlinePresentation != null) {
+ * presentationsBuilder.setInlinePresentation(inlinePresentation);
+ * }
+ * if (dialogPresentation != null) {
+ * presentationsBuilder.setDialogPresentation(dialogPresentation);
+ * }
+ * fieldBuilder.setPresentations(presentationsBuilder.build());
+ * dataset.setField(id, fieldBuilder.build());
+ * </pre>
+ *
+ * @see Field
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param field the fill information about the field.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) {
+ throwIfDestroyed();
+ if (field == null) {
+ setLifeTheUniverseAndEverything(id, null, null, null, null, null, null);
+ } else {
+ final DatasetFieldFilter filter = field.getFilter();
+ final Presentations presentations = field.getPresentations();
+ if (presentations == null) {
+ setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null,
+ filter, null);
+ } else {
+ setLifeTheUniverseAndEverything(id, field.getValue(),
+ presentations.getMenuPresentation(),
+ presentations.getInlinePresentation(),
+ presentations.getInlineTooltipPresentation(), filter,
+ presentations.getDialogPresentation());
+ }
+ }
return this;
}
@@ -793,39 +943,34 @@
* @throws IllegalStateException if {@link #build()} was already called.
*
* @return this builder.
- *
+ * @deprecated Use {@link #setField(AutofillId, Field)} instead.
* @hide
*/
+ @Deprecated
@SystemApi
public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable Pattern filter,
@NonNull InlinePresentation inlinePresentation) {
throwIfDestroyed();
Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, null, inlinePresentation,
- new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null,
+ new DatasetFieldFilter(filter), null);
return this;
}
private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
- @Nullable DatasetFieldFilter filter) {
- setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
- filter);
- }
-
- private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
- @Nullable AutofillValue value, @Nullable RemoteViews presentation,
- @Nullable InlinePresentation inlinePresentation,
@Nullable InlinePresentation tooltip,
- @Nullable DatasetFieldFilter filter) {
+ @Nullable DatasetFieldFilter filter,
+ @Nullable RemoteViews dialogPresentation) {
Objects.requireNonNull(id, "id cannot be null");
if (mFieldIds != null) {
final int existingIdx = mFieldIds.indexOf(id);
if (existingIdx >= 0) {
mFieldValues.set(existingIdx, value);
mFieldPresentations.set(existingIdx, presentation);
+ mFieldDialogPresentations.set(existingIdx, dialogPresentation);
mFieldInlinePresentations.set(existingIdx, inlinePresentation);
mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
mFieldFilters.set(existingIdx, filter);
@@ -835,6 +980,7 @@
mFieldIds = new ArrayList<>();
mFieldValues = new ArrayList<>();
mFieldPresentations = new ArrayList<>();
+ mFieldDialogPresentations = new ArrayList<>();
mFieldInlinePresentations = new ArrayList<>();
mFieldInlineTooltipPresentations = new ArrayList<>();
mFieldFilters = new ArrayList<>();
@@ -842,6 +988,7 @@
mFieldIds.add(id);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
+ mFieldDialogPresentations.add(dialogPresentation);
mFieldInlinePresentations.add(inlinePresentation);
mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
@@ -853,10 +1000,7 @@
* <p>You should not interact with this builder once this method is called.
*
* @throws IllegalStateException if no field was set (through
- * {@link #setValue(AutofillId, AutofillValue)} or
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}),
- * or if {@link #build()} was already called.
+ * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called.
*
* @return The built dataset.
*/
@@ -897,11 +1041,13 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mDialogPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeTypedList(mFieldPresentations, flags);
+ parcel.writeTypedList(mFieldDialogPresentations, flags);
parcel.writeTypedList(mFieldInlinePresentations, flags);
parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
parcel.writeTypedList(mFieldFilters, flags);
@@ -913,8 +1059,12 @@
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@Override
public Dataset createFromParcel(Parcel parcel) {
- final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
- final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+ final RemoteViews presentation = parcel.readParcelable(null,
+ android.widget.RemoteViews.class);
+ final RemoteViews dialogPresentation = parcel.readParcelable(null,
+ android.widget.RemoteViews.class);
+ final InlinePresentation inlinePresentation = parcel.readParcelable(null,
+ android.service.autofill.InlinePresentation.class);
final InlinePresentation inlineTooltipPresentation =
parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
final ArrayList<AutofillId> ids =
@@ -923,27 +1073,41 @@
parcel.createTypedArrayList(AutofillValue.CREATOR);
final ArrayList<RemoteViews> presentations =
parcel.createTypedArrayList(RemoteViews.CREATOR);
+ final ArrayList<RemoteViews> dialogPresentations =
+ parcel.createTypedArrayList(RemoteViews.CREATOR);
final ArrayList<InlinePresentation> inlinePresentations =
parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<InlinePresentation> inlineTooltipPresentations =
parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<DatasetFieldFilter> filters =
parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
- final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
- final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+ final ClipData fieldContent = parcel.readParcelable(null,
+ android.content.ClipData.class);
+ final IntentSender authentication = parcel.readParcelable(null,
+ android.content.IntentSender.class);
final String datasetId = parcel.readString();
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
- final Builder builder = (presentation != null) ? new Builder(presentation)
- : new Builder();
- if (inlinePresentation != null) {
- if (inlineTooltipPresentation != null) {
- builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation);
- } else {
- builder.setInlinePresentation(inlinePresentation);
+ final Builder builder;
+ if (presentation != null || inlinePresentation != null || dialogPresentation != null) {
+ final Presentations.Builder presentationsBuilder = new Presentations.Builder();
+ if (presentation != null) {
+ presentationsBuilder.setMenuPresentation(presentation);
}
+ if (inlinePresentation != null) {
+ presentationsBuilder.setInlinePresentation(inlinePresentation);
+ }
+ if (inlineTooltipPresentation != null) {
+ presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation);
+ }
+ if (dialogPresentation != null) {
+ presentationsBuilder.setDialogPresentation(dialogPresentation);
+ }
+ builder = new Builder(presentationsBuilder.build());
+ } else {
+ builder = new Builder();
}
if (fieldContent != null) {
@@ -954,13 +1118,15 @@
final AutofillId id = ids.get(i);
final AutofillValue value = values.get(i);
final RemoteViews fieldPresentation = presentations.get(i);
+ final RemoteViews fieldDialogPresentation = dialogPresentations.get(i);
final InlinePresentation fieldInlinePresentation =
i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
final InlinePresentation fieldInlineTooltipPresentation =
i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null;
final DatasetFieldFilter filter = filters.get(i);
builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
- fieldInlinePresentation, fieldInlineTooltipPresentation, filter);
+ fieldInlinePresentation, fieldInlineTooltipPresentation, filter,
+ fieldDialogPresentation);
}
builder.setAuthentication(authentication);
builder.setId(datasetId);
@@ -986,7 +1152,7 @@
@Nullable
public final Pattern pattern;
- private DatasetFieldFilter(@Nullable Pattern pattern) {
+ DatasetFieldFilter(@Nullable Pattern pattern) {
this.pattern = pattern;
}
diff --git a/core/java/android/service/autofill/Field.java b/core/java/android/service/autofill/Field.java
new file mode 100644
index 0000000..b7c0d82
--- /dev/null
+++ b/core/java/android/service/autofill/Field.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.DataClass;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to set all information of a field. Such as the
+ * {@link AutofillId} of the field, the {@link AutofillValue} to be autofilled,
+ * a <a href="#Filtering">explicit filter</a>, and presentations to be visualized,
+ * etc.
+ */
+public final class Field {
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ private @Nullable AutofillValue mValue;
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ *
+ * @see Dataset.DatasetFieldFilter
+ * @hide
+ */
+ private @Nullable Dataset.DatasetFieldFilter mFilter;
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ private @Nullable Presentations mPresentations;
+
+
+ /* package-private */ Field(
+ @Nullable AutofillValue value,
+ @Nullable Dataset.DatasetFieldFilter filter,
+ @Nullable Presentations presentations) {
+ this.mValue = value;
+ this.mFilter = filter;
+ this.mPresentations = presentations;
+ }
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ @DataClass.Generated.Member
+ public @Nullable AutofillValue getValue() {
+ return mValue;
+ }
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ *
+ * @see Dataset.DatasetFieldFilter
+ * @hide
+ */
+ public @Nullable Dataset.DatasetFieldFilter getFilter() {
+ return mFilter;
+ }
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ public @Nullable Presentations getPresentations() {
+ return mPresentations;
+ }
+
+ /**
+ * A builder for {@link Field}
+ */
+ public static final class Builder {
+
+ private @Nullable AutofillValue mValue = null;
+ private @Nullable Dataset.DatasetFieldFilter mFilter = null;
+ private @Nullable Presentations mPresentations = null;
+ private boolean mDestroyed = false;
+
+ public Builder() {
+ }
+
+ /**
+ * The value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if the
+ * dataset needs authentication and you have no access to the value.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillValue value) {
+ checkNotUsed();
+ mValue = value;
+ return this;
+ }
+
+ /**
+ * Regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder setFilter(@Nullable Pattern value) {
+ checkNotUsed();
+ mFilter = new Dataset.DatasetFieldFilter(value);
+ return this;
+ }
+
+ /**
+ * The presentations used to visualize this field in Autofill UI.
+ */
+ public @NonNull Builder setPresentations(@NonNull Presentations value) {
+ checkNotUsed();
+ mPresentations = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Field build() {
+ checkNotUsed();
+ mDestroyed = true; // Mark builder used
+
+ Field o = new Field(
+ mValue,
+ mFilter,
+ mPresentations);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if (mDestroyed) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 43bd410..e4d3732 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -98,6 +99,12 @@
// The flag value 0x20 has been defined in AutofillManager.
+ /**
+ * Indicates the request is coming from the activity just started.
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_ACTIVITY_START = 0x40;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -154,19 +161,32 @@
*/
private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+ /**
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
+ */
+ private final @Nullable IntentSender mDelayedFillIntentSender;
+
private void onConstructed() {
Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
}
- // Code below generated by codegen v1.0.15.
+ // 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/service/autofill/FillRequest.java
+ // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -178,7 +198,8 @@
FLAG_MANUAL_REQUEST,
FLAG_COMPATIBILITY_MODE_REQUEST,
FLAG_PASSWORD_INPUT_TYPE,
- FLAG_VIEW_NOT_FOCUSED
+ FLAG_VIEW_NOT_FOCUSED,
+ FLAG_ACTIVITY_START
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -202,6 +223,8 @@
return "FLAG_PASSWORD_INPUT_TYPE";
case FLAG_VIEW_NOT_FOCUSED:
return "FLAG_VIEW_NOT_FOCUSED";
+ case FLAG_ACTIVITY_START:
+ return "FLAG_ACTIVITY_START";
default: return Integer.toHexString(value);
}
}
@@ -243,6 +266,16 @@
*
* <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support
* for inline suggestions.</p>
+ * @param delayedFillIntentSender
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
* @hide
*/
@DataClass.Generated.Member
@@ -251,7 +284,8 @@
@NonNull List<FillContext> fillContexts,
@Nullable Bundle clientState,
@RequestFlags int flags,
- @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
+ @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
+ @Nullable IntentSender delayedFillIntentSender) {
this.mId = id;
this.mFillContexts = fillContexts;
com.android.internal.util.AnnotationValidations.validate(
@@ -264,8 +298,10 @@
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
- | FLAG_VIEW_NOT_FOCUSED);
+ | FLAG_VIEW_NOT_FOCUSED
+ | FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+ this.mDelayedFillIntentSender = delayedFillIntentSender;
onConstructed();
}
@@ -338,6 +374,22 @@
return mInlineSuggestionsRequest;
}
+ /**
+ * Gets the {@link IntentSender} to send a delayed fill response.
+ *
+ * <p>The autofill service must first indicate that it wants to return a delayed
+ * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful
+ * fill response. Then it can use this IntentSender to send an Intent with extra
+ * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p>
+ *
+ * <p>Note that this may be null if a delayed fill response is not supported for
+ * this fill request.</p>
+ */
+ @DataClass.Generated.Member
+ public @Nullable IntentSender getDelayedFillIntentSender() {
+ return mDelayedFillIntentSender;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -349,7 +401,8 @@
"fillContexts = " + mFillContexts + ", " +
"clientState = " + mClientState + ", " +
"flags = " + requestFlagsToString(mFlags) + ", " +
- "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
+ "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " +
+ "delayedFillIntentSender = " + mDelayedFillIntentSender +
" }";
}
@@ -362,12 +415,14 @@
byte flg = 0;
if (mClientState != null) flg |= 0x4;
if (mInlineSuggestionsRequest != null) flg |= 0x10;
+ if (mDelayedFillIntentSender != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeInt(mId);
dest.writeParcelableList(mFillContexts, flags);
if (mClientState != null) dest.writeBundle(mClientState);
dest.writeInt(mFlags);
if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
+ if (mDelayedFillIntentSender != null) dest.writeTypedObject(mDelayedFillIntentSender, flags);
}
@Override
@@ -384,10 +439,11 @@
byte flg = in.readByte();
int id = in.readInt();
List<FillContext> fillContexts = new ArrayList<>();
- in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
+ in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
int flags = in.readInt();
InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+ IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR);
this.mId = id;
this.mFillContexts = fillContexts;
@@ -401,8 +457,10 @@
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
| FLAG_PASSWORD_INPUT_TYPE
- | FLAG_VIEW_NOT_FOCUSED);
+ | FLAG_VIEW_NOT_FOCUSED
+ | FLAG_ACTIVITY_START);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+ this.mDelayedFillIntentSender = delayedFillIntentSender;
onConstructed();
}
@@ -422,10 +480,10 @@
};
@DataClass.Generated(
- time = 1589280816805L,
- codegenVersion = "1.0.15",
+ time = 1643386870464L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d94988e..296877a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -65,10 +65,22 @@
*/
public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+ /**
+ * Flag used to request to wait for a delayed fill from the remote Autofill service if it's
+ * passed to {@link Builder#setFlags(int)}.
+ *
+ * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send
+ * a {@link FillResponse} to the latest {@link FillRequest} via
+ * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback}
+ * has timed out.
+ */
+ public static final int FLAG_DELAY_FILL = 0x4;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_TRACK_CONTEXT_COMMITED,
- FLAG_DISABLE_ACTIVITY_ONLY
+ FLAG_DISABLE_ACTIVITY_ONLY,
+ FLAG_DELAY_FILL
})
@Retention(RetentionPolicy.SOURCE)
@interface FillResponseFlags {}
@@ -79,11 +91,14 @@
private final @Nullable RemoteViews mPresentation;
private final @Nullable InlinePresentation mInlinePresentation;
private final @Nullable InlinePresentation mInlineTooltipPresentation;
+ private final @Nullable RemoteViews mDialogPresentation;
+ private final @Nullable RemoteViews mDialogHeader;
private final @Nullable RemoteViews mHeader;
private final @Nullable RemoteViews mFooter;
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final @Nullable AutofillId[] mFillDialogTriggerIds;
private final long mDisableDuration;
private final @Nullable AutofillId[] mFieldClassificationIds;
private final int mFlags;
@@ -99,10 +114,13 @@
mPresentation = builder.mPresentation;
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
+ mDialogPresentation = builder.mDialogPresentation;
+ mDialogHeader = builder.mDialogHeader;
mHeader = builder.mHeader;
mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
+ mFillDialogTriggerIds = builder.mFillDialogTriggerIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
mFieldClassificationIds = builder.mFieldClassificationIds;
@@ -144,6 +162,16 @@
}
/** @hide */
+ public @Nullable RemoteViews getDialogPresentation() {
+ return mDialogPresentation;
+ }
+
+ /** @hide */
+ public @Nullable RemoteViews getDialogHeader() {
+ return mDialogHeader;
+ }
+
+ /** @hide */
public @Nullable RemoteViews getHeader() {
return mHeader;
}
@@ -164,6 +192,11 @@
}
/** @hide */
+ public @Nullable AutofillId[] getFillDialogTriggerIds() {
+ return mFillDialogTriggerIds;
+ }
+
+ /** @hide */
public @Nullable AutofillId[] getIgnoredIds() {
return mIgnoredIds;
}
@@ -229,6 +262,8 @@
private RemoteViews mPresentation;
private InlinePresentation mInlinePresentation;
private InlinePresentation mInlineTooltipPresentation;
+ private RemoteViews mDialogPresentation;
+ private RemoteViews mDialogHeader;
private RemoteViews mHeader;
private RemoteViews mFooter;
private IntentSender mAuthentication;
@@ -236,6 +271,7 @@
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
private AutofillId[] mFieldClassificationIds;
+ private AutofillId[] mFillDialogTriggerIds;
private int mFlags;
private boolean mDestroyed;
private UserData mUserData;
@@ -243,7 +279,7 @@
private boolean mSupportsInlineSuggestions;
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -277,7 +313,7 @@
* example a credit card whose CVV needs to be entered.
*
* <p>If you provide an authentication intent you must also provide a presentation
- * which is used to visualize visualize the response for triggering the authentication
+ * which is used to visualize the response for triggering the authentication
* flow.
*
* <p><b>Note:</b> Do not make the provided pending intent
@@ -306,7 +342,11 @@
* {@link #setFooter(RemoteViews) footer} are already set for this builder.
*
* @see android.app.PendingIntent#getIntentSender()
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
@@ -327,7 +367,7 @@
}
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p><b>Note:</b> Although the name of this method suggests that it should be used just for
@@ -365,7 +405,11 @@
* {@link #setFooter(RemoteViews) footer} are already set for this builder.
*
* @see android.app.PendingIntent#getIntentSender()
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -374,13 +418,18 @@
}
/**
- * Triggers a custom UI before before autofilling the screen with any data set in this
+ * Triggers a custom UI before autofilling the screen with any data set in this
* response.
*
* <p>This method like
* {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)}
* but allows setting an {@link InlinePresentation} for the inline suggestion tooltip.
+ *
+ * @deprecated Use
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}
+ * instead.
*/
+ @Deprecated
@NonNull
public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@@ -388,6 +437,105 @@
@Nullable InlinePresentation inlineTooltipPresentation) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
+ return setAuthentication(ids, authentication, presentation,
+ inlinePresentation, inlineTooltipPresentation, null);
+ }
+
+ /**
+ * Triggers a custom UI before autofilling the screen with any data set in this
+ * response.
+ *
+ * <p><b>Note:</b> Although the name of this method suggests that it should be used just for
+ * authentication flow, it can be used for other advanced flows; see {@link AutofillService}
+ * for examples.
+ *
+ * <p>This is typically useful when a user interaction is required to unlock their
+ * data vault if you encrypt the data set labels and data set data. It is recommended
+ * to encrypt only the sensitive data and not the data set labels which would allow
+ * auth on the data set level leading to a better user experience. Note that if you
+ * use sensitive data as a label, for example an email address, then it should also
+ * be encrypted. The provided {@link android.app.PendingIntent intent} must be an
+ * {@link Activity} which implements your authentication flow. Also if you provide an auth
+ * intent you also need to specify the presentation view to be shown in the fill UI
+ * for the user to trigger your authentication flow.
+ *
+ * <p>When a user triggers autofill, the system launches the provided intent
+ * whose extras will have the
+ * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
+ * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
+ * client state}. Once you complete your authentication flow you should set the
+ * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
+ * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
+ * with the fully populated {@link FillResponse response} (or {@code null} if the screen
+ * cannot be autofilled).
+ *
+ * <p>For example, if you provided an empty {@link FillResponse response} because the
+ * user's data was locked and marked that the response needs an authentication then
+ * in the response returned if authentication succeeds you need to provide all
+ * available data sets some of which may need to be further authenticated, for
+ * example a credit card whose CVV needs to be entered.
+ *
+ * <p>If you provide an authentication intent you must also provide a presentation
+ * which is used to visualize the response for triggering the authentication
+ * flow.
+ *
+ * <p><b>Note:</b> Do not make the provided pending intent
+ * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
+ * platform needs to fill in the authentication arguments.
+ *
+ * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does
+ * not work with {@link InlinePresentation}.</p>
+ *
+ * @param ids id of Views that when focused will display the authentication UI.
+ * @param authentication Intent to an activity with your authentication flow.
+ * @param presentations The presentations to visualize the response.
+ *
+ * @throws IllegalArgumentException if any of the following occurs:
+ * <ul>
+ * <li>{@code ids} is {@code null}</li>
+ * <li>{@code ids} is empty</li>
+ * <li>{@code ids} contains a {@code null} element</li>
+ * <li>{@code authentication} is {@code null}, but either or both of
+ * {@code presentations.getPresentation()} and
+ * {@code presentations.getInlinePresentation()} is non-{@code null}</li>
+ * <li>{@code authentication} is non-{{@code null}, but both
+ * {@code presentations.getPresentation()} and
+ * {@code presentations.getInlinePresentation()} are {@code null}</li>
+ * </ul>
+ *
+ * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a
+ * {@link #setFooter(RemoteViews) footer} are already set for this builder.
+ *
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication,
+ @Nullable Presentations presentations) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
+ if (presentations == null) {
+ return setAuthentication(ids, authentication, null, null, null, null);
+ }
+ return setAuthentication(ids, authentication,
+ presentations.getMenuPresentation(),
+ presentations.getInlinePresentation(),
+ presentations.getInlineTooltipPresentation(),
+ presentations.getDialogPresentation());
+ }
+
+ /**
+ * Triggers a custom UI before autofilling the screen with any data set in this
+ * response.
+ */
+ @NonNull
+ private Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable InlinePresentation inlineTooltipPresentation,
+ @Nullable RemoteViews dialogPresentation) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (mHeader != null || mFooter != null) {
throw new IllegalStateException("Already called #setHeader() or #setFooter()");
}
@@ -400,6 +548,7 @@
mPresentation = presentation;
mInlinePresentation = inlinePresentation;
mInlineTooltipPresentation = inlineTooltipPresentation;
+ mDialogPresentation = dialogPresentation;
mAuthenticationIds = assertValid(ids);
return this;
}
@@ -520,7 +669,7 @@
public Builder setFlags(@FillResponseFlags int flags) {
throwIfDestroyed();
mFlags = Preconditions.checkFlagsArgument(flags,
- FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+ FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
return this;
}
@@ -552,7 +701,7 @@
*
* @throws IllegalArgumentException if {@code duration} is not a positive number.
* @throws IllegalStateException if either {@link #addDataset(Dataset)},
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
* {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or
* {@link #setFieldClassificationIds(AutofillId...)} was already called.
*/
@@ -591,8 +740,8 @@
* @return this builder
*
* @throws IllegalStateException if an
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was
- * already set for this builder.
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
+ * authentication} was already set for this builder.
*/
// TODO(b/69796626): make it sticky / update javadoc
@NonNull
@@ -623,7 +772,7 @@
* @return this builder
*
* @throws IllegalStateException if the FillResponse
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
* requires authentication}.
*/
// TODO(b/69796626): make it sticky / update javadoc
@@ -643,7 +792,7 @@
*
* @return this builder
* @throws IllegalStateException if the FillResponse
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)
* requires authentication}.
*/
@NonNull
@@ -674,13 +823,46 @@
}
/**
+ * Sets the presentation of header in fill dialog UI. The header should have
+ * a prompt for what datasets are shown in the dialog. If this is not set,
+ * the dialog only shows your application icon.
+ *
+ * More details about the fill dialog, see
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ */
+ @NonNull
+ public Builder setDialogHeader(@NonNull RemoteViews header) {
+ throwIfDestroyed();
+ Objects.requireNonNull(header);
+ mDialogHeader = header;
+ return this;
+ }
+
+ /**
+ * Sets which fields are used for the fill dialog UI.
+ *
+ * More details about the fill dialog, see
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ * @throws NullPointerException if {@code ids} or any element on it is {@code null}.
+ */
+ @NonNull
+ public Builder setFillDialogTriggerIds(@NonNull AutofillId... ids) {
+ throwIfDestroyed();
+ Preconditions.checkArrayElementsNotNull(ids, "ids");
+ mFillDialogTriggerIds = ids;
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
* <ol>
* <li>{@link #build()} was already called.
* <li>No call was made to {@link #addDataset(Dataset)},
- * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)},
* {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)},
* {@link #setClientState(Bundle)},
* or {@link #setFieldClassificationIds(AutofillId...)}.
@@ -767,6 +949,12 @@
if (mInlineTooltipPresentation != null) {
builder.append(", hasInlineTooltipPresentation");
}
+ if (mDialogPresentation != null) {
+ builder.append(", hasDialogPresentation");
+ }
+ if (mDialogHeader != null) {
+ builder.append(", hasDialogHeader");
+ }
if (mHeader != null) {
builder.append(", hasHeader");
}
@@ -779,6 +967,10 @@
if (mAuthenticationIds != null) {
builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
}
+ if (mFillDialogTriggerIds != null) {
+ builder.append(", fillDialogTriggerIds=")
+ .append(Arrays.toString(mFillDialogTriggerIds));
+ }
builder.append(", disableDuration=").append(mDisableDuration);
if (mFlags != 0) {
builder.append(", flags=").append(mFlags);
@@ -815,6 +1007,9 @@
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeParcelable(mInlineTooltipPresentation, flags);
+ parcel.writeParcelable(mDialogPresentation, flags);
+ parcel.writeParcelable(mDialogHeader, flags);
+ parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
parcel.writeParcelable(mUserData, flags);
@@ -850,9 +1045,18 @@
final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+ final RemoteViews dialogPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
if (authenticationIds != null) {
builder.setAuthentication(authenticationIds, authentication, presentation,
- inlinePresentation, inlineTooltipPresentation);
+ inlinePresentation, inlineTooltipPresentation, dialogPresentation);
+ }
+ final RemoteViews dialogHeader = parcel.readParcelable(null, android.widget.RemoteViews.class);
+ if (dialogHeader != null) {
+ builder.setDialogHeader(dialogHeader);
+ }
+ final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
+ if (triggerIds != null) {
+ builder.setFillDialogTriggerIds(triggerIds);
}
final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
if (header != null) {
diff --git a/core/java/android/service/autofill/Presentations.java b/core/java/android/service/autofill/Presentations.java
new file mode 100644
index 0000000..e8ac628
--- /dev/null
+++ b/core/java/android/service/autofill/Presentations.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Holds presentations used to visualize autofill suggestions for each available UI type.
+ *
+ * @see Field
+ */
+@DataClass(genBuilder = true)
+public final class Presentations {
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Note: Before Android 13, this was referred to simply as "presentation" in the SDK.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ private @Nullable RemoteViews mMenuPresentation;
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ private @Nullable InlinePresentation mInlinePresentation;
+
+ /**
+ * The presentation used to visualize this field in the
+ * <a href="Dataset.html#FillDialogUI">fill dialog UI</a>
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ private @Nullable RemoteViews mDialogPresentation;
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+ private static RemoteViews defaultMenuPresentation() {
+ return null;
+ }
+
+ private static InlinePresentation defaultInlinePresentation() {
+ return null;
+ }
+
+ private static RemoteViews defaultDialogPresentation() {
+ return null;
+ }
+
+ private static InlinePresentation defaultInlineTooltipPresentation() {
+ return null;
+ }
+
+ private void onConstructed() {
+ if (mMenuPresentation == null
+ && mInlinePresentation == null
+ && mDialogPresentation == null) {
+ throw new IllegalStateException("All presentations are null.");
+ }
+ if (mInlineTooltipPresentation != null && mInlinePresentation == null) {
+ throw new IllegalStateException(
+ "The inline presentation is required for mInlineTooltipPresentation.");
+ }
+ }
+
+
+
+ // 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/service/autofill/Presentations.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 */ Presentations(
+ @Nullable RemoteViews menuPresentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable RemoteViews dialogPresentation,
+ @Nullable InlinePresentation inlineTooltipPresentation) {
+ this.mMenuPresentation = menuPresentation;
+ this.mInlinePresentation = inlinePresentation;
+ this.mDialogPresentation = dialogPresentation;
+ this.mInlineTooltipPresentation = inlineTooltipPresentation;
+
+ onConstructed();
+ }
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RemoteViews getMenuPresentation() {
+ return mMenuPresentation;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlinePresentation getInlinePresentation() {
+ return mInlinePresentation;
+ }
+
+ /**
+ * The presentation used to visualize this field in the fill dialog UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @Nullable RemoteViews getDialogPresentation() {
+ return mDialogPresentation;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlinePresentation getInlineTooltipPresentation() {
+ return mInlineTooltipPresentation;
+ }
+
+ /**
+ * A builder for {@link Presentations}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable RemoteViews mMenuPresentation;
+ private @Nullable InlinePresentation mInlinePresentation;
+ private @Nullable RemoteViews mDialogPresentation;
+ private @Nullable InlinePresentation mInlineTooltipPresentation;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The presentation used to visualize this field in fill UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMenuPresentation(@NonNull RemoteViews value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mMenuPresentation = value;
+ return this;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to visualize this dataset as inline suggestions.
+ * If the dataset supports inline suggestions, this should not be null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlinePresentation(@NonNull InlinePresentation value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mInlinePresentation = value;
+ return this;
+ }
+
+ /**
+ * The presentation used to visualize this field in the fill dialog UI.
+ *
+ * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color
+ * or background color: Autofill on different platforms may have different themes.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDialogPresentation(@NonNull RemoteViews value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mDialogPresentation = value;
+ return this;
+ }
+
+ /**
+ * The {@link InlinePresentation} used to show the tooltip for the
+ * {@code mInlinePresentation}. If the set this field, the
+ * {@code mInlinePresentation} should not be null.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlineTooltipPresentation(@NonNull InlinePresentation value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mInlineTooltipPresentation = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Presentations build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mMenuPresentation = defaultMenuPresentation();
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mInlinePresentation = defaultInlinePresentation();
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mDialogPresentation = defaultDialogPresentation();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mInlineTooltipPresentation = defaultInlineTooltipPresentation();
+ }
+ Presentations o = new Presentations(
+ mMenuPresentation,
+ mInlinePresentation,
+ mDialogPresentation,
+ mInlineTooltipPresentation);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1643083242164L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/Presentations.java",
+ inputSignatures = "private @android.annotation.Nullable android.widget.RemoteViews mMenuPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlinePresentation\nprivate @android.annotation.Nullable android.widget.RemoteViews mDialogPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlineTooltipPresentation\nprivate static android.widget.RemoteViews defaultMenuPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlinePresentation()\nprivate static android.widget.RemoteViews defaultDialogPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlineTooltipPresentation()\nprivate void onConstructed()\nclass Presentations extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/service/cloudsearch/ICloudSearchService.aidl
similarity index 71%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/service/cloudsearch/ICloudSearchService.aidl
index 861a4ed..104bf99 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/service/cloudsearch/ICloudSearchService.aidl
@@ -14,6 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.service.cloudsearch;
-parcelable DeviceInfo;
+import android.app.cloudsearch.SearchRequest;
+
+/**
+ * Interface from the system to CloudSearch service.
+ *
+ * @hide
+ */
+oneway interface ICloudSearchService {
+ void onSearch(in SearchRequest request);
+}
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index 870a7e3..9df8358 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -16,9 +16,11 @@
package android.service.games;
+import android.Manifest;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.IGameManagerService;
@@ -173,6 +175,7 @@
*
* @param taskId The taskId of the game.
*/
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public final void createGameSession(@IntRange(from = 0) int taskId) {
if (mGameServiceController == null) {
throw new IllegalStateException("Can not call before connected()");
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index f4baedc..468e087c 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -20,15 +20,23 @@
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.app.ActivityTaskManager;
+import android.app.Instrumentation;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.SurfaceControlViewHost;
import android.view.View;
@@ -41,6 +49,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -84,6 +93,15 @@
}
@Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+ GameSession::dispatchTransientSystemBarVisibilityFromRevealGestureChanged,
+ GameSession.this,
+ visibleDueToGesture));
+ }
+
+ @Override
public void onTaskFocusChanged(boolean focused) {
Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
GameSession::moveToState, GameSession.this,
@@ -109,7 +127,9 @@
}
private LifecycleState mLifecycleState = LifecycleState.INITIALIZED;
+ private boolean mAreTransientInsetsVisibleDueToGesture = false;
private IGameSessionController mGameSessionController;
+ private Context mContext;
private int mTaskId;
private GameSessionRootView mGameSessionRootView;
private SurfaceControlViewHost mSurfaceControlViewHost;
@@ -127,6 +147,7 @@
int heightPx) {
mGameSessionController = gameSessionController;
mTaskId = taskId;
+ mContext = context;
mSurfaceControlViewHost = surfaceControlViewHost;
mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost);
surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx);
@@ -138,11 +159,23 @@
}
@Hide
- void doDestroy() {
+ private void doDestroy() {
mSurfaceControlViewHost.release();
moveToState(LifecycleState.DESTROYED);
}
+ /** @hide */
+ @VisibleForTesting
+ @MainThread
+ public void dispatchTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ boolean didValueChange = mAreTransientInsetsVisibleDueToGesture != visibleDueToGesture;
+ mAreTransientInsetsVisibleDueToGesture = visibleDueToGesture;
+ if (didValueChange) {
+ onTransientSystemBarVisibilityFromRevealGestureChanged(visibleDueToGesture);
+ }
+ }
+
/**
* @hide
*/
@@ -252,7 +285,23 @@
*
* @param focused True if the game task is focused, false if the game task is unfocused.
*/
- public void onGameTaskFocusChanged(boolean focused) {}
+ public void onGameTaskFocusChanged(boolean focused) {
+ }
+
+ /**
+ * Called when the visibility of the transient system bars changed due to the user performing
+ * the reveal gesture. The reveal gesture is defined as a swipe to reveal the transient system
+ * bars that originates from the system bars.
+ *
+ * @param visibleDueToGesture if the transient bars triggered by the reveal gesture are visible.
+ * This is {@code true} when the transient system bars become visible
+ * due to user performing the reveal gesture. This is {@code false}
+ * when the transient system bars are hidden or become permanently
+ * visible.
+ */
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ }
/**
* Sets the task overlay content to an explicit view. This view is placed directly into the game
@@ -261,6 +310,8 @@
* {@code View} may not be cleared once set, but may be replaced by invoking
* {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again.
*
+ * <p><b>WARNING</b>: Callers <b>must</b> ensure that only trusted views are provided.
+ *
* @param view The desired content to display.
* @param layoutParams Layout parameters for the view.
*/
@@ -278,7 +329,7 @@
*
* @return {@code true} if the game was successfully restarted; otherwise, {@code false}.
*/
- @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)
public final boolean restartGame() {
try {
mGameSessionController.restartGame(mTaskId);
@@ -344,12 +395,14 @@
/**
* Called when taking the screenshot failed.
+ *
* @param statusCode Indicates the reason for failure.
*/
void onFailure(@ScreenshotFailureStatus int statusCode);
/**
* Called when taking the screenshot succeeded.
+ *
* @param bitmap The screenshot.
*/
void onSuccess(@NonNull Bitmap bitmap);
@@ -416,4 +469,67 @@
break;
}
}
+
+ /**
+ * Launches an activity within the same activity stack as the {@link GameSession}. When the
+ * target activity exits, {@link GameSessionActivityCallback#onActivityResult(int, Intent)} will
+ * be invoked with the result code and result data directly from the target activity (in other
+ * words, the result code and data set via the target activity's
+ * {@link android.app.Activity#startActivityForResult} call). The caller is expected to handle
+ * the results that the target activity returns.
+ *
+ * <p>Any activity that an app would normally be able to start via {@link
+ * android.app.Activity#startActivityForResult} will be startable via this method.
+ *
+ * <p>Started activities may see a different calling package than the game session's package
+ * when calling {@link android.app.Activity#getCallingPackage()}.
+ *
+ * <p> If an exception is thrown while handling {@code intent},
+ * {@link GameSessionActivityCallback#onActivityStartFailed(Throwable)} will be called instead
+ * of {@link GameSessionActivityCallback#onActivityResult(int, Intent)}.
+ *
+ * @param intent The intent to start.
+ * @param options Additional options for how the Activity should be started. See
+ * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle)} for
+ * more details. This value may be null.
+ * @param executor Executor on which {@code callback} should be invoked.
+ * @param callback Callback to be invoked once the started activity has finished.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)
+ public final void startActivityFromGameSessionForResult(
+ @NonNull Intent intent, @Nullable Bundle options, @NonNull Executor executor,
+ @NonNull GameSessionActivityCallback callback) {
+ Objects.requireNonNull(intent);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ AndroidFuture<GameSessionActivityResult> future =
+ new AndroidFuture<GameSessionActivityResult>()
+ .whenCompleteAsync((result, ex) -> {
+ if (ex != null) {
+ callback.onActivityStartFailed(ex);
+ return;
+ }
+ callback.onActivityResult(result.getResultCode(), result.getData());
+ }, executor);
+
+ final Intent trampolineIntent = new Intent();
+ trampolineIntent.setComponent(
+ new ComponentName(
+ "android", "android.service.games.GameSessionTrampolineActivity"));
+ trampolineIntent.putExtra(GameSessionTrampolineActivity.INTENT_KEY, intent);
+ trampolineIntent.putExtra(GameSessionTrampolineActivity.OPTIONS_KEY, options);
+ trampolineIntent.putExtra(
+ GameSessionTrampolineActivity.FUTURE_KEY, future);
+
+ try {
+ int result = ActivityTaskManager.getService().startActivityFromGameSession(
+ mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession",
+ Binder.getCallingPid(), Binder.getCallingUid(), trampolineIntent, mTaskId,
+ UserHandle.myUserId());
+ Instrumentation.checkStartActivityResult(result, trampolineIntent);
+ } catch (Throwable t) {
+ executor.execute(() -> callback.onActivityStartFailed(t));
+ }
+ }
}
diff --git a/core/java/android/service/games/GameSessionActivityCallback.java b/core/java/android/service/games/GameSessionActivityCallback.java
new file mode 100644
index 0000000..3b11df1
--- /dev/null
+++ b/core/java/android/service/games/GameSessionActivityCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.games;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Callback invoked when an activity launched via
+ * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor,
+ * GameSessionActivityCallback)}} has returned a result or failed to start.
+ *
+ * @hide
+ */
+@SystemApi
+public interface GameSessionActivityCallback {
+ /**
+ * Callback invoked when an activity launched via
+ * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor,
+ * GameSessionActivityCallback)}} has returned a result.
+ *
+ * @param resultCode The result code of the launched activity. See {@link
+ * android.app.Activity#setResult(int)}.
+ * @param data Any data returned by the launched activity. See {@link
+ * android.app.Activity#setResult(int, Intent)}.
+ */
+ void onActivityResult(int resultCode, @Nullable Intent data);
+
+ /**
+ * Callback invoked when a throwable was thrown when launching the {@link Intent} in
+ * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor,
+ * GameSessionActivityCallback)}}.
+ */
+ default void onActivityStartFailed(@NonNull Throwable t) {}
+}
diff --git a/core/java/android/service/games/GameSessionActivityResult.java b/core/java/android/service/games/GameSessionActivityResult.java
new file mode 100644
index 0000000..a2ec6ad
--- /dev/null
+++ b/core/java/android/service/games/GameSessionActivityResult.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+final class GameSessionActivityResult implements Parcelable {
+
+ public static final Creator<GameSessionActivityResult> CREATOR =
+ new Creator<GameSessionActivityResult>() {
+ @Override
+ public GameSessionActivityResult createFromParcel(Parcel in) {
+ int resultCode = in.readInt();
+ Intent data = in.readParcelable(Intent.class.getClassLoader(), Intent.class);
+ return new GameSessionActivityResult(resultCode, data);
+ }
+
+ @Override
+ public GameSessionActivityResult[] newArray(int size) {
+ return new GameSessionActivityResult[size];
+ }
+ };
+
+ private final int mResultCode;
+ @Nullable
+ private final Intent mData;
+
+ GameSessionActivityResult(int resultCode, @Nullable Intent data) {
+ mResultCode = resultCode;
+ mData = data;
+ }
+
+ int getResultCode() {
+ return mResultCode;
+ }
+
+ @Nullable
+ Intent getData() {
+ return mData;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mResultCode);
+ dest.writeParcelable(mData, flags);
+ }
+}
diff --git a/core/java/android/service/games/GameSessionTrampolineActivity.java b/core/java/android/service/games/GameSessionTrampolineActivity.java
new file mode 100644
index 0000000..ddea098
--- /dev/null
+++ b/core/java/android/service/games/GameSessionTrampolineActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Trampoline activity that enables the
+ * {@link GameSession#startActivityFromGameSessionForResult(Intent, Bundle, Executor,
+ * GameSessionActivityCallback)} API by reusing existing activity result infrastructure in the
+ * {@link Activity} class. This activity forwards activity results back to the calling
+ * {@link GameSession} via {@link AndroidFuture}.
+ *
+ * @hide
+ */
+public final class GameSessionTrampolineActivity extends Activity {
+ private static final String TAG = "GameSessionTrampoline";
+ private static final int REQUEST_CODE = 1;
+
+ static final String FUTURE_KEY = "GameSessionTrampolineActivity.future";
+ static final String INTENT_KEY = "GameSessionTrampolineActivity.intent";
+ static final String OPTIONS_KEY = "GameSessionTrampolineActivity.options";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+ startActivityAsCaller(
+ getIntent().getParcelableExtra(INTENT_KEY),
+ getIntent().getBundleExtra(OPTIONS_KEY),
+ null,
+ false,
+ getUserId(),
+ REQUEST_CODE);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to launch activity from game session");
+ AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra(
+ FUTURE_KEY);
+ future.completeExceptionally(e);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_CODE) {
+ // Something went very wrong if we hit this code path, and we should bail.
+ throw new IllegalStateException("Unexpected request code: " + requestCode);
+ }
+
+ AndroidFuture<GameSessionActivityResult> future = getIntent().getParcelableExtra(
+ FUTURE_KEY);
+ future.complete(new GameSessionActivityResult(resultCode, data));
+ finish();
+ }
+}
diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl
index 71da630..49c36c6 100644
--- a/core/java/android/service/games/IGameSession.aidl
+++ b/core/java/android/service/games/IGameSession.aidl
@@ -21,5 +21,6 @@
*/
oneway interface IGameSession {
void onDestroyed();
+ void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean visibleDueToGesture);
void onTaskFocusChanged(boolean focused);
}
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/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 31352f1..11e5ad8 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -37,5 +37,6 @@
boolean getOemUnlockEnabled();
int getFlashLockState();
boolean hasFrpCredentialHandle();
+ String getPersistentDataPackageName();
}
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 44a8862..9167153 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -26,8 +26,6 @@
import android.os.RemoteException;
import android.service.oemlock.OemLockManager;
-import com.android.internal.R;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,7 +51,6 @@
@SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE)
public class PersistentDataBlockManager {
private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
- private final Context mContext;
private IPersistentDataBlockService sService;
/**
@@ -78,10 +75,7 @@
public @interface FlashLockState {}
/** @hide */
- public PersistentDataBlockManager(
- Context context,
- IPersistentDataBlockService service) {
- mContext = context;
+ public PersistentDataBlockManager(IPersistentDataBlockService service) {
sService = service;
}
@@ -219,7 +213,12 @@
*/
@SystemApi
@NonNull
+ @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE)
public String getPersistentDataPackageName() {
- return mContext.getString(R.string.config_persistentDataPackageName);
+ try {
+ return sService.getPersistentDataPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
similarity index 68%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
index 861a4ed..f6c47dd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
@@ -14,6 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.service.selectiontoolbar;
-parcelable DeviceInfo;
+import android.os.IBinder;
+
+/**
+ * The interface from the SelectionToolbarRenderService to the system.
+ *
+ * @hide
+ */
+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/tracing/TraceReportService.java b/core/java/android/service/tracing/TraceReportService.java
new file mode 100644
index 0000000..3d16a3d
--- /dev/null
+++ b/core/java/android/service/tracing/TraceReportService.java
@@ -0,0 +1,165 @@
+/*
+ * 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.service.tracing;
+
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.tracing.TraceReportParams;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Service to be sub-classed and exposed by (privileged) apps which want to report
+ * system traces.
+ * <p>
+ * Subclasses should implement the onReportTrace method to handle traces reported
+ * to them.
+ * </p>
+ * <pre>
+ * public class SampleReportService extends TraceReportService {
+ * public void onReportTrace(TraceParams args) {
+ * // --- Implementation goes here ---
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The service declaration in the application manifest must specify
+ * BIND_TRACE_REPORT_SERVICE in the permission attribute.
+ * </p>
+ * <pre>
+ * <application>
+ * <service android:name=".SampleReportService"
+ * android:permission="android.permission.BIND_TRACE_REPORT_SERVICE">
+ * </service>
+ * </application>
+ * </pre>
+ *
+ * Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS
+ * permissions.
+ *
+ * @hide
+ */
+@SystemApi(client = PRIVILEGED_APPS)
+public class TraceReportService extends Service {
+ private static final String TAG = "TraceReportService";
+ private Messenger mMessenger = null;
+
+ /**
+ * Public to allow this to be used by TracingServiceProxy in system_server.
+ *
+ * @hide
+ */
+ public static final int MSG_REPORT_TRACE = 1;
+
+ /**
+ * Contains information about the trace which is being reported.
+ *
+ * @hide
+ */
+ @SystemApi(client = PRIVILEGED_APPS)
+ public static final class TraceParams {
+ private final ParcelFileDescriptor mFd;
+ private final UUID mUuid;
+
+ private TraceParams(TraceReportParams params) {
+ mFd = params.fd;
+ mUuid = new UUID(params.uuidMsb, params.uuidLsb);
+ }
+
+ /**
+ * Returns the ParcelFileDescriptor for the collected trace.
+ */
+ @NonNull
+ public ParcelFileDescriptor getFd() {
+ return mFd;
+ }
+
+ /**
+ * Returns the UUID of the trace; this is exactly the UUID created by the tracing system
+ * (i.e. Perfetto) and is also present inside the trace file.
+ */
+ @NonNull
+ public UUID getUuid() {
+ return mUuid;
+ }
+ }
+
+ // Methods to override.
+ /**
+ * Called when a trace is reported and sent to this class.
+ *
+ * Note: the trace file descriptor should not be persisted beyond the lifetime of this
+ * function as it is owned by the framework and will be closed immediately after this function
+ * returns: if future use of the fd is needed, it should be duped.
+ */
+ public void onReportTrace(@NonNull TraceParams args) {
+ }
+
+ // Optional methods to override.
+ // Realistically, these methods are internal implementation details but since this class is
+ // a SystemApi, it's better to err on the side of flexibility just in-case we need to override
+ // these methods down the line.
+
+ /**
+ * Handles binder calls from system_server.
+ */
+ public boolean onMessage(@NonNull Message msg) {
+ if (msg.what == MSG_REPORT_TRACE) {
+ if (!(msg.obj instanceof TraceReportParams)) {
+ Log.e(TAG, "Received invalid type for report trace message.");
+ return false;
+ }
+ TraceParams params = new TraceParams((TraceReportParams) msg.obj);
+ try {
+ onReportTrace(params);
+ } finally {
+ try {
+ params.getFd().close();
+ } catch (IOException ignored) {
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns an IBinder for handling binder calls from system_server.
+ */
+ @Nullable
+ @Override
+ public IBinder onBind(@NonNull Intent intent) {
+ if (mMessenger == null) {
+ mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
+ }
+ return mMessenger.getBinder();
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
index 220e498..6b11e74 100644
--- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -26,6 +26,7 @@
oneway interface ITrustAgentServiceCallback {
void grantTrust(CharSequence message, long durationMs, int flags);
void revokeTrust();
+ void lockUser();
void setManagingTrust(boolean managingTrust);
void onConfigureCompleted(boolean result, IBinder token);
void addEscrowToken(in byte[] token, int userId);
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index fba61cf..8f6e1e0 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -119,16 +119,15 @@
* automatically remove trust after some conditions are met (detailed below) with the option for
* the agent to renew the trust again later.
*
- * <p>After this is called, the agent will grant trust until the platform thinks an active user
- * is no longer using that trust. For example, if the user dismisses keyguard, the platform will
- * remove trust (this does not automatically lock the device).
+ * <p>After this is called, the agent will grant trust until the platform thinks an active
+ * user is no longer using that trust. This can happen for any reason as determined by the
+ * platform. For example, if the user dismisses keyguard, the platform will remove trust;
+ * since this does not automatically lock the device, this results in the device locking the
+ * next time the screen turns off.
*
* <p>When the platform internally removes the agent's trust in this manner, an agent can
* re-grant it (via a call to grantTrust) without the user having to unlock the device through
* another method (e.g. PIN). This renewable state only persists for a limited time.
- *
- * TODO(b/213631675): Remove @hide
- * @hide
*/
public static final int FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE = 1 << 2;
@@ -139,9 +138,6 @@
* Without this flag, the message passed to {@code grantTrust} is only used for debugging
* purposes. With the flag, it may be displayed to the user as the reason why the device is
* unlocked.
- *
- * TODO(b/213911325): Remove @hide
- * @hide
*/
public static final int FLAG_GRANT_TRUST_DISPLAY_MESSAGE = 1 << 3;
@@ -309,9 +305,6 @@
* {@link #grantTrust(CharSequence, long, int)}.
*
* @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
- *
- * TODO(b/213631672): Add CTS tests
- * @hide
*/
public void onUserRequestedUnlock() {
}
@@ -624,11 +617,15 @@
*
* If the user has no auth method specified, then keyguard will still be shown but can be
* dismissed normally.
- *
- * TODO(b/213631675): Implement & make public
- * @hide
*/
public final void lockUser() {
+ if (mCallback != null) {
+ try {
+ mCallback.lockUser();
+ } catch (RemoteException e) {
+ onError("calling lockUser");
+ }
+ }
}
/**
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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f2a0355..c91851a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -905,11 +905,12 @@
if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
return;
}
+
+ SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
if (!isPreview() && mShouldDim) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
- SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
// Animate dimming to gradually change the wallpaper alpha from the previous
// dim amount to the new amount only if the dim amount changed.
@@ -919,16 +920,15 @@
? 0 : DIMMING_ANIMATION_DURATION_MS);
animator.addUpdateListener((ValueAnimator va) -> {
final float dimValue = (float) va.getAnimatedValue();
- surfaceControl
- .setAlpha(mBbqSurfaceControl, 1 - dimValue)
- .apply();
+ if (mBbqSurfaceControl != null) {
+ surfaceControlTransaction
+ .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply();
+ }
});
animator.start();
} else {
Log.v(TAG, "Setting wallpaper dimming: " + 0);
- new SurfaceControl.Transaction()
- .setAlpha(mBbqSurfaceControl, 1.0f)
- .apply();
+ surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply();
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
index 861a4ed..ca75d2e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/android/service/wallpapereffectsgeneration/IWallpaperEffectsGenerationService.aidl
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2022 The Android Open Source Project
*
@@ -14,6 +15,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package android.service.wallpapereffectsgeneration;
-parcelable DeviceInfo;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+/**
+ * Interface from the system to WallpaperEffectsGeneration service.
+ *
+ * @hide
+ */
+oneway interface IWallpaperEffectsGenerationService {
+ void onGenerateCinematicEffect(in CinematicEffectRequest request);
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..18b654e
--- /dev/null
+++ b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.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 android.service.wallpapereffectsgeneration;
+
+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.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+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.util.Log;
+import android.util.Slog;
+
+/**
+ * A service for handling wallpaper effects generation tasks. It must implement
+ * (onGenerateCinematicEffect} method to generate response and call returnCinematicEffectResponse
+ * to send the response.
+ *
+ * <p>To extend this service, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_WALLPAPER_EFFECTS_GENERATION} permission and includes
+ * an intent filter with the {@link #SERVICE_INTERFACE} action. For example: </p>
+ * <pre>
+ * <application>
+ * <service android:name=".CtsWallpaperEffectsGenerationService"
+ * android:exported="true"
+ * android:label="CtsWallpaperEffectsGenerationService"
+ * android:permission="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService"
+ />
+ * </intent-filter>
+ * </service>
+ * <uses-library android:name="android.test.runner"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class WallpaperEffectsGenerationService 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_WALLPAPER_EFFECTS_GENERATION}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WallpaperEffectsGenerationService";
+ private Handler mHandler;
+ private IWallpaperEffectsGenerationManager mService;
+
+ private final IWallpaperEffectsGenerationService mInterface =
+ new IWallpaperEffectsGenerationService.Stub() {
+ @Override
+ public void onGenerateCinematicEffect(CinematicEffectRequest request) {
+ mHandler.sendMessage(
+ obtainMessage(
+ WallpaperEffectsGenerationService::onGenerateCinematicEffect,
+ WallpaperEffectsGenerationService.this, request));
+ }
+ };
+
+ /**
+ * Called when the OS receives a request for generating cinematic effect. On receiving the
+ * request, it extract cinematic information from the input and call
+ * {@link #returnCinematicEffectResponse} with the textured mesh
+ * and metadata wrapped in CinematicEffectResponse.
+ *
+ * @param request the cinematic effect request passed from the client.
+ */
+ public abstract void onGenerateCinematicEffect(@NonNull CinematicEffectRequest request);
+
+ /**
+ * Returns the cinematic effect response. Must be called when cinematic effect
+ * response is generated and ready to be sent back. Otherwise the response won't be
+ * returned.
+ *
+ * @param response the cinematic effect response returned from service provider.
+ */
+ public final void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+ try {
+ mService.returnCinematicEffectResponse(response);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) {
+ Log.d(TAG, "onCreate WallpaperEffectsGenerationService");
+ }
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ IBinder b = ServiceManager.getService(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ mService = IWallpaperEffectsGenerationManager.Stub.asInterface(b);
+ }
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "onBind WallpaperEffectsGenerationService");
+ }
+ 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/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/tracing/ITracingServiceProxy.aidl b/core/java/android/tracing/ITracingServiceProxy.aidl
index 4520db3..8029b88 100644
--- a/core/java/android/tracing/ITracingServiceProxy.aidl
+++ b/core/java/android/tracing/ITracingServiceProxy.aidl
@@ -16,17 +16,25 @@
package android.tracing;
+import android.tracing.TraceReportParams;
+
/**
* Binder interface for the TracingServiceProxy running in system_server.
*
* {@hide}
*/
-interface ITracingServiceProxy
-{
+interface ITracingServiceProxy {
/**
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
* there is no buffer available to dump.
*/
oneway void notifyTraceSessionEnded(boolean sessionStolen);
+
+ /**
+ * Notifies the specified service that a trace has been captured. The contents of |params|
+ * contains the intended recipient (package and class) of this trace as well as a file
+ * descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE).
+ */
+ oneway void reportTrace(in TraceReportParams params);
}
diff --git a/core/java/android/tracing/TraceReportParams.aidl b/core/java/android/tracing/TraceReportParams.aidl
new file mode 100644
index 0000000..f57386c
--- /dev/null
+++ b/core/java/android/tracing/TraceReportParams.aidl
@@ -0,0 +1,59 @@
+/**
+ * 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.tracing;
+
+import android.os.ParcelFileDescriptor;
+
+/*
+ * Parameters for a trace report.
+ *
+ * See ITracingServiceProxy::reportTrace for more details.
+ *
+ * @hide
+ */
+parcelable TraceReportParams {
+ // The package name containing the reporter service (see |reporterClassName|).
+ String reporterPackageName;
+
+ // The class name of the reporter service. The framework will bind to this service and pass the
+ // trace fd and metadata to this class.
+ // This class should be "trusted" (in practice this means being a priv_app + having DUMP and
+ // USAGE_STATS permissions).
+ String reporterClassName;
+
+ // The file descriptor for the trace file. This will be an unlinked file fd (i.e. created
+ // with O_TMPFILE); the intention is that reporter classes link this fd into a app-private
+ // folder for reporting when conditions are right (e.g. charging, on unmetered networks etc).
+ ParcelFileDescriptor fd;
+
+ // The least-significant-bytes of the UUID of this trace.
+ long uuidLsb;
+
+ // The most-significant-bytes of the UUID of this trace.
+ long uuidMsb;
+
+ // Flag indicating whether, instead of passing the fd from the trace collector, to pass a
+ // pipe fd from system_server and send the file over it.
+ //
+ // This flag is necessary because there is no good way to write a CTS test where a helper
+ // priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be
+ // preinstalled on the system partition). By creating a pipe in system_server we work around
+ // this restriction. Note that there is a maximum allowed file size if this flag is set
+ // (see TracingServiceProxy). Further note that, even though SELinux may be worked around,
+ // manifest (i.e. framework) permissions are still checked even if this flag is set.
+ boolean usePipeForTesting;
+}
\ No newline at end of file
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/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
index b5e8dd7..5bb263a 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -15,7 +15,12 @@
*/
package android.util;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE;
+
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -37,6 +42,7 @@
protected final Context mContext;
protected final PackageManager mPm;
protected final UserManager mUm;
+ protected final DevicePolicyManager mDpm;
protected final LauncherIcons mLauncherIcons;
protected final boolean mEmbedShadow;
@@ -44,6 +50,7 @@
mContext = context;
mPm = context.getPackageManager();
mUm = context.getSystemService(UserManager.class);
+ mDpm = context.getSystemService(DevicePolicyManager.class);
mLauncherIcons = new LauncherIcons(context);
mEmbedShadow = embedShadow;
}
@@ -73,18 +80,32 @@
if (appInfo.isInstantApp()) {
int badgeColor = Resources.getSystem().getColor(
com.android.internal.R.color.instant_app_badge, null);
+ Drawable badge = mContext.getDrawable(
+ com.android.internal.R.drawable.ic_instant_icon_badge_bolt);
icon = mLauncherIcons.getBadgedDrawable(icon,
- com.android.internal.R.drawable.ic_instant_icon_badge_bolt,
+ badge,
badgeColor);
}
if (mUm.hasBadge(userId)) {
- icon = mLauncherIcons.getBadgedDrawable(icon,
- mUm.getUserIconBadgeResId(userId),
- mUm.getUserBadgeColor(userId));
+
+ Drawable badge = mDpm.getDrawable(
+ getUpdatableUserIconBadgeId(userId),
+ SOLID_COLORED,
+ () -> getDefaultUserIconBadge(userId));
+
+ icon = mLauncherIcons.getBadgedDrawable(icon, badge, mUm.getUserBadgeColor(userId));
}
return icon;
}
+ private String getUpdatableUserIconBadgeId(int userId) {
+ return mUm.isManagedProfile(userId) ? WORK_PROFILE_ICON_BADGE : UNDEFINED;
+ }
+
+ private Drawable getDefaultUserIconBadge(int userId) {
+ return mContext.getResources().getDrawable(mUm.getUserIconBadgeResId(userId));
+ }
+
/**
* Add shadow to the icon if {@link AdaptiveIconDrawable}
*/
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index e652e17..355b2e9 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -45,10 +45,12 @@
private final SparseArray<Bitmap> mShadowCache = new SparseArray<>();
private final int mIconSize;
private final Resources mRes;
+ private final Context mContext;
public LauncherIcons(Context context) {
mRes = context.getResources();
mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
+ mContext = context;
}
public Drawable wrapIconDrawableWithShadow(Drawable drawable) {
@@ -98,14 +100,14 @@
return shadow;
}
- public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) {
- return getBadgedDrawable(null, foregroundRes, backgroundColor);
+ public Drawable getBadgeDrawable(Drawable badgeForeground, int backgroundColor) {
+ return getBadgedDrawable(null, badgeForeground, backgroundColor);
}
- public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
+ public Drawable getBadgedDrawable(
+ Drawable base, Drawable badgeForeground, int backgroundColor) {
Resources overlayableRes =
ActivityThread.currentActivityThread().getApplication().getResources();
-
// ic_corp_icon_badge_shadow is not work-profile-specific.
Drawable badgeShadow = overlayableRes.getDrawable(
com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
@@ -115,7 +117,6 @@
com.android.internal.R.drawable.ic_corp_icon_badge_color)
.getConstantState().newDrawable().mutate();
- Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes);
badgeForeground.setTint(backgroundColor);
Drawable[] drawables = base == null
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/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index c7d9b9c..c8c1fd4 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -407,20 +407,6 @@
}
}
- static byte[] generateApkVerityRootHash(String apkPath)
- throws IOException, SignatureNotFoundException, DigestException,
- NoSuchAlgorithmException {
- try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
- SignatureInfo signatureInfo = findSignature(apk);
- VerifiedSigner vSigner = verify(apk, false);
- if (vSigner.verityRootHash == null) {
- return null;
- }
- return VerityBuilder.generateApkVerityRootHash(
- apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
- }
- }
-
/**
* Verified APK Signature Scheme v2 signer.
*
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 7e65d61..3287ce8 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -545,20 +545,6 @@
}
}
- static byte[] generateApkVerityRootHash(String apkPath)
- throws NoSuchAlgorithmException, DigestException, IOException,
- SignatureNotFoundException {
- try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
- SignatureInfo signatureInfo = findSignature(apk);
- VerifiedSigner vSigner = verify(apk, false);
- if (vSigner.verityRootHash == null) {
- return null;
- }
- return VerityBuilder.generateApkVerityRootHash(
- apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
- }
- }
-
/**
* Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
*
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index bff5426..d2a18dd 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -569,27 +569,6 @@
}
/**
- * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
- * in Signing Block.
- *
- * @return FSverity root hash
- */
- public static byte[] generateApkVerityRootHash(String apkPath)
- throws NoSuchAlgorithmException, DigestException, IOException {
- // first try v3
- try {
- return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath);
- } catch (SignatureNotFoundException e) {
- // try older version
- }
- try {
- return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath);
- } catch (SignatureNotFoundException e) {
- return null;
- }
- }
-
- /**
* Extended signing details.
* @hide for internal use only.
*/
diff --git a/core/java/android/util/apk/VerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java
index c7c465d..adf53c2 100644
--- a/core/java/android/util/apk/VerityBuilder.java
+++ b/core/java/android/util/apk/VerityBuilder.java
@@ -143,25 +143,6 @@
return generateFsVerityTreeInternal(apk, salt, levelOffset, tree);
}
}
- /**
- * Calculates the apk-verity root hash for integrity measurement. This needs to be consistent
- * to what kernel returns.
- */
- @NonNull
- static byte[] generateApkVerityRootHash(@NonNull RandomAccessFile apk,
- @NonNull ByteBuffer apkDigest, @NonNull SignatureInfo signatureInfo)
- throws NoSuchAlgorithmException, DigestException, IOException {
- assertSigningBlockAlignedAndHasFullPages(signatureInfo);
-
- ByteBuffer footer = ByteBuffer.allocate(CHUNK_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN);
- generateApkVerityFooter(apk, signatureInfo, footer);
- footer.flip();
-
- MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
- md.update(footer);
- md.update(apkDigest);
- return md.digest();
- }
/**
* Generates the apk-verity header and hash tree to be used by kernel for the given apk. This
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index d6e074f..246a8c9 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);
}
@@ -1123,6 +1118,19 @@
}
/**
+ * Returns the system's preferred display mode. This mode will be used when the user has not
+ * specified a display-mode preference. This returns null if the boot display mode feature is
+ * not supported by system.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public Display.Mode getSystemPreferredDisplayMode() {
+ return mGlobal.getSystemPreferredDisplayMode(getDisplayId());
+ }
+
+ /**
* Returns the display's HDR capabilities.
*
* @see #isHdr()
@@ -1468,8 +1476,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 +1546,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 +1673,6 @@
}
}
}
-
- mMayAdjustByFixedRotation = mResources != null
- && mResources.hasOverrideDisplayAdjustments();
}
private void updateCachedAppSizeIfNeededLocked() {
@@ -1679,6 +1686,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 +1736,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/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 449e9b3..67ae743 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -63,5 +63,5 @@
/**
* Called when the keep clear ares on a display have changed.
*/
- void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas);
+ void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
}
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/InputMonitor.java b/core/java/android/view/InputMonitor.java
index ad1f201..8801fe0 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -79,13 +79,17 @@
- // Code below generated by codegen v1.0.7.
+ // 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/InputMonitor.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -126,7 +130,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(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) { ... }
@@ -141,7 +145,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ InputMonitor(Parcel in) {
+ /* package-private */ InputMonitor(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -167,17 +171,21 @@
}
@Override
- public InputMonitor createFromParcel(Parcel in) {
+ public InputMonitor createFromParcel(@NonNull Parcel in) {
return new InputMonitor(in);
}
};
@DataClass.Generated(
- time = 1571177265149L,
- codegenVersion = "1.0.7",
+ time = 1637697281750L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java
index 05c312b..f3ca531 100644
--- a/core/java/android/view/OnBackInvokedDispatcher.java
+++ b/core/java/android/view/OnBackInvokedDispatcher.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.os.Build;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,6 +33,13 @@
* target (a.k.a. the callback to be invoked next), or its behavior.
*/
public abstract class OnBackInvokedDispatcher {
+
+ /** @hide */
+ public static final String TAG = "OnBackInvokedDispatcher";
+
+ /** @hide */
+ public static final boolean DEBUG = Build.isDebuggable();
+
/** @hide */
@IntDef({
PRIORITY_DEFAULT,
diff --git a/core/java/android/view/OnBackInvokedDispatcherOwner.java b/core/java/android/view/OnBackInvokedDispatcherOwner.java
index 0e14ed4..e69efe0 100644
--- a/core/java/android/view/OnBackInvokedDispatcherOwner.java
+++ b/core/java/android/view/OnBackInvokedDispatcherOwner.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
/**
* A class that provides an {@link OnBackInvokedDispatcher} that allows you to register
@@ -28,6 +28,6 @@
* to its registered {@link OnBackInvokedCallback}s.
* Returns null when the root view is not attached to a window or a view tree with a decor.
*/
- @Nullable
+ @NonNull
OnBackInvokedDispatcher getOnBackInvokedDispatcher();
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 904d7c8..6f5fea2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -197,6 +197,9 @@
private static native int[] nativeGetCompositionDataspaces();
private static native boolean nativeSetActiveColorMode(IBinder displayToken,
int colorMode);
+ private static native boolean nativeGetBootDisplayModeSupport();
+ private static native void nativeSetBootDisplayMode(IBinder displayToken, int displayMode);
+ private static native void nativeClearBootDisplayMode(IBinder displayToken);
private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on);
private static native void nativeSetGameContentType(IBinder displayToken, boolean on);
private static native void nativeSetDisplayPowerMode(
@@ -1878,6 +1881,8 @@
public boolean autoLowLatencyModeSupported;
public boolean gameContentTypeSupported;
+ public int preferredBootDisplayMode;
+
@Override
public String toString() {
return "DynamicDisplayInfo{"
@@ -1887,7 +1892,8 @@
+ ", activeColorMode=" + activeColorMode
+ ", hdrCapabilities=" + hdrCapabilities
+ ", autoLowLatencyModeSupported=" + autoLowLatencyModeSupported
- + ", gameContentTypeSupported" + gameContentTypeSupported + "}";
+ + ", gameContentTypeSupported" + gameContentTypeSupported
+ + ", preferredBootDisplayMode" + preferredBootDisplayMode + "}";
}
@Override
@@ -1899,7 +1905,8 @@
&& activeDisplayModeId == that.activeDisplayModeId
&& Arrays.equals(supportedColorModes, that.supportedColorModes)
&& activeColorMode == that.activeColorMode
- && Objects.equals(hdrCapabilities, that.hdrCapabilities);
+ && Objects.equals(hdrCapabilities, that.hdrCapabilities)
+ && preferredBootDisplayMode == that.preferredBootDisplayMode;
}
@Override
@@ -2266,6 +2273,36 @@
/**
* @hide
*/
+ public static boolean getBootDisplayModeSupport() {
+ return nativeGetBootDisplayModeSupport();
+ }
+
+ /** There is no associated getter for this method. When this is set, the display is expected
+ * to start up in this mode next time the device reboots.
+ * @hide
+ */
+ public static void setBootDisplayMode(IBinder displayToken, int displayModeId) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ nativeSetBootDisplayMode(displayToken, displayModeId);
+ }
+
+ /**
+ * @hide
+ */
+ public static void clearBootDisplayMode(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ nativeClearBootDisplayMode(displayToken);
+ }
+
+ /**
+ * @hide
+ */
public static void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 85a9dbd..7e0d887 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -22,10 +22,13 @@
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.os.RemoteException;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.view.InsetsState;
import java.util.Objects;
@@ -71,6 +74,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();
@@ -168,6 +181,36 @@
return mRemoteInterface;
}
+ /**
+ * Forward a configuration to the remote SurfaceControlViewHost.
+ * This will cause View#onConfigurationChanged to be invoked on the remote
+ * end. This does not automatically cause the SurfaceControlViewHost
+ * to be resized. The root View of a SurfaceControlViewHost
+ * is more akin to a PopupWindow in that the size is user specified
+ * independent of configuration width and height.
+ *
+ * @param c The configuration to forward
+ */
+ public void notifyConfigurationChanged(@NonNull Configuration c) {
+ try {
+ getRemoteInterface().onConfigurationChanged(c);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Tear down the remote SurfaceControlViewHost and cause
+ * View#onDetachedFromWindow to be invoked on the other side.
+ */
+ public void notifyDetachedFromWindow() {
+ try {
+ getRemoteInterface().onDispatchDetachedFromWindow();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
@Override
public int describeContents() {
return 0;
@@ -274,7 +317,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..22c66dc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -834,7 +834,7 @@
*/
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
- AccessibilityEventSource, OnBackInvokedDispatcherOwner {
+ AccessibilityEventSource {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private static final boolean DBG = false;
@@ -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;
}
}
@@ -8169,9 +8178,13 @@
// to User. Ideally View should handle the event when isVisibleToUser()
// becomes true where it should issue notifyViewEntered().
afm.notifyViewEntered(this);
+ } else {
+ afm.enableFillRequestActivityStarted();
}
} else if (!enter && !isFocused()) {
afm.notifyViewExited(this);
+ } else if (enter) {
+ afm.enableFillRequestActivityStarted();
}
}
}
@@ -12277,7 +12290,7 @@
/**
* @return whether this view should have haptic feedback enabled for events
- * long presses.
+ * such as long presses.
*
* @see #setHapticFeedbackEnabled(boolean)
* @see #performHapticFeedback(int)
@@ -31118,6 +31131,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.
*
@@ -31398,23 +31447,4 @@
}
return null;
}
-
- /**
- * Returns the {@link OnBackInvokedDispatcher} instance of the window this view is attached to.
- *
- * @return The {@link OnBackInvokedDispatcher} or {@code null} if the view is neither attached
- * to a window or a view tree with a decor.
- */
- @Nullable
- public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
- ViewParent parent = getParent();
- if (parent instanceof View) {
- return ((View) parent).getOnBackInvokedDispatcher();
- } else if (parent instanceof ViewRootImpl) {
- // Get the fallback dispatcher on {@link ViewRootImpl} if the view tree doesn't have
- // a {@link com.android.internal.policy.DecorView}.
- return ((ViewRootImpl) parent).getOnBackInvokedDispatcher();
- }
- return null;
- }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1496a4a..3d86535 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -236,7 +236,7 @@
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
- AttachedSurfaceControl {
+ AttachedSurfaceControl, OnBackInvokedDispatcherOwner {
private static final String TAG = "ViewRootImpl";
private static final boolean DBG = false;
private static final boolean LOCAL_LOGV = false;
@@ -313,9 +313,9 @@
private @SurfaceControl.BufferTransform
int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
/**
- * The fallback {@link OnBackInvokedDispatcher} when the window doesn't have a decor view.
+ * The top level {@link OnBackInvokedDispatcher}.
*/
- private WindowOnBackInvokedDispatcher mFallbackOnBackInvokedDispatcher =
+ private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher =
new WindowOnBackInvokedDispatcher();
/**
@@ -563,6 +563,8 @@
@Nullable
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
+ boolean mPerformAutoFill;
+
boolean mReportNextDraw;
/**
@@ -643,6 +645,7 @@
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ Rect mOverrideInsetsFrame;
final Rect mPendingBackDropFrame = new Rect();
@@ -841,6 +844,7 @@
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
+ mPerformAutoFill = true;
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
@@ -889,7 +893,6 @@
mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
- mFallbackOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -1140,9 +1143,6 @@
if (pendingInsetsController != null) {
pendingInsetsController.replayAndAttach(mInsetsController);
}
- ((RootViewSurfaceTaker) mView)
- .provideWindowOnBackInvokedDispatcher()
- .attachToWindow(mWindowSession, mWindow);
}
try {
@@ -1189,6 +1189,7 @@
getAttachedWindowFrame(), 1f /* compactScale */,
mTmpFrames.displayFrame, mTempRect2, mTmpFrames.frame);
setFrame(mTmpFrames.frame);
+ registerBackCallbackOnWindow();
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
@@ -4352,6 +4353,18 @@
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
+
+ if (mPerformAutoFill) {
+ notifyEnterForAutoFillIfNeeded();
+ }
+ }
+
+ private void notifyEnterForAutoFillIfNeeded() {
+ mPerformAutoFill = false;
+ final AutofillManager afm = getAutofillManager();
+ if (afm != null) {
+ afm.notifyViewEnteredForActivityStarted(mView);
+ }
}
/**
@@ -8069,7 +8082,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);
}
/**
@@ -8386,6 +8414,7 @@
mAdded = false;
}
+ mOnBackInvokedDispatcher.detachFromWindow();
WindowManagerGlobal.getInstance().doRemoveView(this);
}
@@ -10740,12 +10769,17 @@
* Returns the {@link OnBackInvokedDispatcher} on the decor view if one exists, or the
* fallback {@link OnBackInvokedDispatcher} instance.
*/
- @Nullable
- public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
- if (mView instanceof RootViewSurfaceTaker) {
- return ((RootViewSurfaceTaker) mView).provideWindowOnBackInvokedDispatcher();
- }
- return mFallbackOnBackInvokedDispatcher;
+ @NonNull
+ public WindowOnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+ return mOnBackInvokedDispatcher;
+ }
+
+ /**
+ * When this ViewRootImpl is added to the window manager, transfers the first
+ * {@link OnBackInvokedCallback} to be called to the server.
+ */
+ private void registerBackCallbackOnWindow() {
+ mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
}
@Override
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index aa9ea19..8401b7c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -295,6 +295,9 @@
private OnWindowDismissedCallback mOnWindowDismissedCallback;
private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback;
private WindowControllerCallback mWindowControllerCallback;
+ @WindowInsetsController.Appearance
+ private int mSystemBarAppearance;
+ private DecorCallback mDecorCallback;
private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
private Rect mRestrictedCaptionAreaRect;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -661,6 +664,35 @@
void updateNavigationBarColor(int color);
}
+ /** @hide */
+ public interface DecorCallback {
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+ *
+ * @param appearance The newly applied appearance.
+ */
+ void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance);
+
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#updateColorViews(WindowInsets, boolean)}
+ * when {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground} is
+ * being updated.
+ *
+ * @param drawLegacyNavigationBarBackground the new value that is being set to
+ * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground}.
+ * @return The value to be set to
+ * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackgroundHandled}
+ * on behalf of the {@link com.android.internal.policy.DecorView}.
+ * {@code true} to tell that the Window can render the legacy navigation bar
+ * background on behalf of the {@link com.android.internal.policy.DecorView}.
+ * {@code false} to let {@link com.android.internal.policy.DecorView} handle it.
+ */
+ boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground);
+ }
+
/**
* Callback for clients that want to be aware of where caption draws content.
*/
@@ -985,6 +1017,36 @@
return mWindowControllerCallback;
}
+ /** @hide */
+ public final void setDecorCallback(DecorCallback decorCallback) {
+ mDecorCallback = decorCallback;
+ }
+
+ /** @hide */
+ @WindowInsetsController.Appearance
+ public final int getSystemBarAppearance() {
+ return mSystemBarAppearance;
+ }
+
+ /** @hide */
+ public final void dispatchOnSystemBarAppearanceChanged(
+ @WindowInsetsController.Appearance int appearance) {
+ mSystemBarAppearance = appearance;
+ if (mDecorCallback != null) {
+ mDecorCallback.onSystemBarAppearanceChanged(appearance);
+ }
+ }
+
+ /** @hide */
+ public final boolean onDrawLegacyNavigationBarBackgroundChanged(
+ boolean drawLegacyNavigationBarBackground) {
+ if (mDecorCallback == null) {
+ return false;
+ }
+ return mDecorCallback.onDrawLegacyNavigationBarBackgroundChanged(
+ drawLegacyNavigationBarBackground);
+ }
+
/**
* Set a callback for changes of area where caption will draw its content.
*
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/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index a427ab8..cd8dd86 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -553,8 +553,20 @@
public static final int TYPE_ASSIST_READING_CONTEXT = 0x01000000;
/**
- * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
- * The type of change is not defined.
+ * Represents a change in the speech state defined by the content-change types. A change in the
+ * speech state occurs when another service is either speaking or listening for human speech.
+ * This event helps avoid conflicts where two services want to speak or one listens
+ * when another speaks.
+ * @see #SPEECH_STATE_SPEAKING_START
+ * @see #SPEECH_STATE_SPEAKING_END
+ * @see #SPEECH_STATE_LISTENING_START
+ * @see #SPEECH_STATE_LISTENING_END
+ */
+ public static final int TYPE_SPEECH_STATE_CHANGE = 0x02000000;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: The type of change is not
+ * defined.
*/
public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;
@@ -641,6 +653,27 @@
*/
public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x0000200;
+ /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
+ public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
+
+ /**
+ * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is no longer
+ * speaking.
+ */
+ public static final int SPEECH_STATE_SPEAKING_END = 0x00000002;
+
+ /**
+ * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is listening to the
+ * microphone.
+ */
+ public static final int SPEECH_STATE_LISTENING_START = 0x00000004;
+
+ /**
+ * Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is no longer
+ * listening to the microphone.
+ */
+ public static final int SPEECH_STATE_LISTENING_END = 0x00000008;
+
/**
* Change type for {@link #TYPE_WINDOWS_CHANGED} event:
* The window was added.
@@ -730,50 +763,69 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = { "CONTENT_CHANGE_TYPE_" },
+ @IntDef(
+ flag = true,
+ prefix = {"CONTENT_CHANGE_TYPE_"},
value = {
- CONTENT_CHANGE_TYPE_UNDEFINED,
- CONTENT_CHANGE_TYPE_SUBTREE,
- CONTENT_CHANGE_TYPE_TEXT,
- CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
- CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
- CONTENT_CHANGE_TYPE_PANE_TITLE,
- CONTENT_CHANGE_TYPE_PANE_APPEARED,
- CONTENT_CHANGE_TYPE_PANE_DISAPPEARED,
- CONTENT_CHANGE_TYPE_DRAG_STARTED,
- CONTENT_CHANGE_TYPE_DRAG_DROPPED,
- CONTENT_CHANGE_TYPE_DRAG_CANCELLED
+ CONTENT_CHANGE_TYPE_UNDEFINED,
+ CONTENT_CHANGE_TYPE_SUBTREE,
+ CONTENT_CHANGE_TYPE_TEXT,
+ CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+ CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
+ CONTENT_CHANGE_TYPE_PANE_TITLE,
+ CONTENT_CHANGE_TYPE_PANE_APPEARED,
+ CONTENT_CHANGE_TYPE_PANE_DISAPPEARED,
+ CONTENT_CHANGE_TYPE_DRAG_STARTED,
+ CONTENT_CHANGE_TYPE_DRAG_DROPPED,
+ CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
})
public @interface ContentChangeTypes {}
/** @hide */
- @IntDef(flag = true, prefix = { "TYPE_" }, value = {
- TYPE_VIEW_CLICKED,
- TYPE_VIEW_LONG_CLICKED,
- TYPE_VIEW_SELECTED,
- TYPE_VIEW_FOCUSED,
- TYPE_VIEW_TEXT_CHANGED,
- TYPE_WINDOW_STATE_CHANGED,
- TYPE_NOTIFICATION_STATE_CHANGED,
- TYPE_VIEW_HOVER_ENTER,
- TYPE_VIEW_HOVER_EXIT,
- TYPE_TOUCH_EXPLORATION_GESTURE_START,
- TYPE_TOUCH_EXPLORATION_GESTURE_END,
- TYPE_WINDOW_CONTENT_CHANGED,
- TYPE_VIEW_SCROLLED,
- TYPE_VIEW_TEXT_SELECTION_CHANGED,
- TYPE_ANNOUNCEMENT,
- TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
- TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
- TYPE_GESTURE_DETECTION_START,
- TYPE_GESTURE_DETECTION_END,
- TYPE_TOUCH_INTERACTION_START,
- TYPE_TOUCH_INTERACTION_END,
- TYPE_WINDOWS_CHANGED,
- TYPE_VIEW_CONTEXT_CLICKED,
- TYPE_ASSIST_READING_CONTEXT
- })
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ flag = true,
+ prefix = {"SPEECH_STATE_"},
+ value = {
+ SPEECH_STATE_SPEAKING_START,
+ SPEECH_STATE_SPEAKING_END,
+ SPEECH_STATE_LISTENING_START,
+ SPEECH_STATE_LISTENING_END
+ })
+ public @interface SpeechStateChangeTypes {}
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ prefix = {"TYPE_"},
+ value = {
+ TYPE_VIEW_CLICKED,
+ TYPE_VIEW_LONG_CLICKED,
+ TYPE_VIEW_SELECTED,
+ TYPE_VIEW_FOCUSED,
+ TYPE_VIEW_TEXT_CHANGED,
+ TYPE_WINDOW_STATE_CHANGED,
+ TYPE_NOTIFICATION_STATE_CHANGED,
+ TYPE_VIEW_HOVER_ENTER,
+ TYPE_VIEW_HOVER_EXIT,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_WINDOW_CONTENT_CHANGED,
+ TYPE_VIEW_SCROLLED,
+ TYPE_VIEW_TEXT_SELECTION_CHANGED,
+ TYPE_ANNOUNCEMENT,
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ TYPE_GESTURE_DETECTION_START,
+ TYPE_GESTURE_DETECTION_END,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_WINDOWS_CHANGED,
+ TYPE_VIEW_CONTEXT_CLICKED,
+ TYPE_ASSIST_READING_CONTEXT,
+ TYPE_SPEECH_STATE_CHANGE
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -814,6 +866,7 @@
int mAction;
int mContentChangeTypes;
int mWindowChangeTypes;
+ int mSpeechStateChangeTypes;
/**
* The stack trace describing where this event originated from on the app side.
@@ -867,6 +920,7 @@
mMovementGranularity = event.mMovementGranularity;
mAction = event.mAction;
mContentChangeTypes = event.mContentChangeTypes;
+ mSpeechStateChangeTypes = event.mSpeechStateChangeTypes;
mWindowChangeTypes = event.mWindowChangeTypes;
mEventTime = event.mEventTime;
mPackageName = event.mPackageName;
@@ -1008,6 +1062,51 @@
}
/**
+ * Gets the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
+ *
+ * @see #SPEECH_STATE_SPEAKING_START
+ * @see #SPEECH_STATE_SPEAKING_END
+ * @see #SPEECH_STATE_LISTENING_START
+ * @see #SPEECH_STATE_LISTENING_END
+ */
+ public int getSpeechStateChangeTypes() {
+ return mSpeechStateChangeTypes;
+ }
+
+ private static String speechStateChangedTypesToString(int types) {
+ return BitUtils.flagsToString(
+ types, AccessibilityEvent::singleSpeechStateChangeTypeToString);
+ }
+
+ private static String singleSpeechStateChangeTypeToString(int type) {
+ switch (type) {
+ case SPEECH_STATE_SPEAKING_START:
+ return "SPEECH_STATE_SPEAKING_START";
+ case SPEECH_STATE_LISTENING_START:
+ return "SPEECH_STATE_LISTENING_START";
+ case SPEECH_STATE_SPEAKING_END:
+ return "SPEECH_STATE_SPEAKING_END";
+ case SPEECH_STATE_LISTENING_END:
+ return "SPEECH_STATE_LISTENING_END";
+ default:
+ return Integer.toHexString(type);
+ }
+ }
+
+ /**
+ * Sets the speech state type signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
+ *
+ * @see #SPEECH_STATE_SPEAKING_START
+ * @see #SPEECH_STATE_SPEAKING_END
+ * @see #SPEECH_STATE_LISTENING_START
+ * @see #SPEECH_STATE_LISTENING_END
+ */
+ public void setSpeechStateChangeTypes(int state) {
+ enforceNotSealed();
+ mSpeechStateChangeTypes = state;
+ }
+
+ /**
* Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A
* single event may represent multiple change types.
*
@@ -1239,6 +1338,7 @@
mAction = 0;
mContentChangeTypes = 0;
mWindowChangeTypes = 0;
+ mSpeechStateChangeTypes = 0;
mPackageName = null;
mEventTime = 0;
if (mRecords != null) {
@@ -1261,6 +1361,7 @@
mAction = parcel.readInt();
mContentChangeTypes = parcel.readInt();
mWindowChangeTypes = parcel.readInt();
+ mSpeechStateChangeTypes = parcel.readInt();
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mEventTime = parcel.readLong();
mConnectionId = parcel.readInt();
@@ -1332,6 +1433,7 @@
parcel.writeInt(mAction);
parcel.writeInt(mContentChangeTypes);
parcel.writeInt(mWindowChangeTypes);
+ parcel.writeInt(mSpeechStateChangeTypes);
TextUtils.writeToParcel(mPackageName, parcel, 0);
parcel.writeLong(mEventTime);
parcel.writeInt(mConnectionId);
@@ -1500,6 +1602,7 @@
case TYPE_WINDOWS_CHANGED: return "TYPE_WINDOWS_CHANGED";
case TYPE_VIEW_CONTEXT_CLICKED: return "TYPE_VIEW_CONTEXT_CLICKED";
case TYPE_ASSIST_READING_CONTEXT: return "TYPE_ASSIST_READING_CONTEXT";
+ case TYPE_SPEECH_STATE_CHANGE: return "TYPE_SPEECH_STATE_CHANGE";
default: return Integer.toHexString(eventType);
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 0a33d6c..a31cacf 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -710,6 +710,8 @@
private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000;
+ private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -2276,6 +2278,38 @@
}
/**
+ * Gets if the node has selectable text.
+ *
+ * <p>
+ * Services should use {@link #ACTION_SET_SELECTION} for selection. Editable text nodes must
+ * also be selectable. But not all UIs will populate this field, so services should consider
+ * 'isTextSelectable | isEditable' to ensure they don't miss nodes with selectable text.
+ * </p>
+ *
+ * @see #isEditable
+ * @return True if the node has selectable text.
+ */
+ public boolean isTextSelectable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE);
+ }
+
+ /**
+ * Sets if the node has selectable text.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param selectableText True if the node has selectable text, false otherwise.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelectable(boolean selectableText) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE, selectableText);
+ }
+
+ /**
* Gets if the node is editable.
*
* @return True if the node is editable, false otherwise.
@@ -4327,8 +4361,12 @@
return "ACTION_CANCEL_DRAG";
case R.id.accessibilityActionDragDrop:
return "ACTION_DROP";
- default:
+ default: {
+ if (action == R.id.accessibilityActionShowSuggestions) {
+ return "ACTION_SHOW_SUGGESTIONS";
+ }
return "ACTION_UNKNOWN";
+ }
}
}
@@ -4462,6 +4500,7 @@
builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
builder.append("; visible: ").append(isVisibleToUser());
builder.append("; actions: ").append(mActions);
+ builder.append("; isTextSelectable: ").append(isTextSelectable());
return builder.toString();
}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index ab749ee..fadbdbb 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -865,6 +865,15 @@
}
/**
+ * @return if a window animation has outsets applied to it.
+ *
+ * @hide
+ */
+ public boolean hasExtension() {
+ return false;
+ }
+
+ /**
* If showBackground is {@code true} and this animation is applied on a window, then the windows
* in the animation will animate with the background associated with this window behind them.
*
@@ -942,6 +951,21 @@
}
/**
+ * Gets the transformation to apply a specific point in time. Implementations of this method
+ * should always be kept in sync with getTransformation.
+ *
+ * @param normalizedTime time between 0 and 1 where 0 is the start of the animation and 1 the
+ * end.
+ * @param outTransformation A transformation object that is provided by the
+ * caller and will be filled in by the animation.
+ * @hide
+ */
+ public void getTransformationAt(float normalizedTime, Transformation outTransformation) {
+ final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
+ applyTransformation(interpolatedTime, outTransformation);
+ }
+
+ /**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
@@ -987,8 +1011,7 @@
normalizedTime = 1.0f - normalizedTime;
}
- final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
- applyTransformation(interpolatedTime, outTransformation);
+ getTransformationAt(normalizedTime, outTransformation);
}
if (expired) {
@@ -1228,18 +1251,19 @@
public float value;
/**
- * Size descriptions can appear inthree forms:
+ * Size descriptions can appear in four forms:
* <ol>
* <li>An absolute size. This is represented by a number.</li>
* <li>A size relative to the size of the object being animated. This
- * is represented by a number followed by "%".</li> *
+ * is represented by a number followed by "%".</li>
* <li>A size relative to the size of the parent of object being
* animated. This is represented by a number followed by "%p".</li>
+ * <li>(Starting from API 32) A complex number.</li>
* </ol>
* @param value The typed value to parse
* @return The parsed version of the description
*/
- static Description parseValue(TypedValue value) {
+ static Description parseValue(TypedValue value, Context context) {
Description d = new Description();
if (value == null) {
d.type = ABSOLUTE;
@@ -1260,6 +1284,11 @@
d.type = ABSOLUTE;
d.value = value.data;
return d;
+ } else if (value.type == TypedValue.TYPE_DIMENSION) {
+ d.type = ABSOLUTE;
+ d.value = TypedValue.complexToDimension(value.data,
+ context.getResources().getDisplayMetrics());
+ return d;
}
}
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 03c6ca6..a2f3544 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -363,6 +363,26 @@
* The transformation of an animation set is the concatenation of all of its
* component animations.
*
+ * @see android.view.animation.Animation#getTransformationAt
+ * @hide
+ */
+ @Override
+ public void getTransformationAt(float interpolatedTime, Transformation t) {
+ final Transformation temp = mTempTransformation;
+
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animation a = mAnimations.get(i);
+
+ temp.clear();
+ a.getTransformationAt(interpolatedTime, t);
+ t.compose(temp);
+ }
+ }
+
+ /**
+ * The transformation of an animation set is the concatenation of all of its
+ * component animations.
+ *
* @see android.view.animation.Animation#getTransformation
*/
@Override
@@ -517,4 +537,15 @@
public boolean willChangeBounds() {
return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
}
+
+ /** @hide */
+ @Override
+ public boolean hasExtension() {
+ for (Animation animation : mAnimations) {
+ if (animation.hasExtension()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 7ce0f45..7d1dc76 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -190,6 +190,8 @@
anim = new TranslateAnimation(c, attrs);
} else if (name.equals("cliprect")) {
anim = new ClipRectAnimation(c, attrs);
+ } else if (name.equals("extend")) {
+ anim = new ExtendAnimation(c, attrs);
} else {
throw new RuntimeException("Unknown animation name: " + parser.getName());
}
diff --git a/core/java/android/view/animation/ClipRectAnimation.java b/core/java/android/view/animation/ClipRectAnimation.java
index 21509d3..3f4b3e7 100644
--- a/core/java/android/view/animation/ClipRectAnimation.java
+++ b/core/java/android/view/animation/ClipRectAnimation.java
@@ -20,7 +20,6 @@
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
/**
* An animation that controls the clip of an object. See the
@@ -66,43 +65,43 @@
com.android.internal.R.styleable.ClipRectAnimation);
Description d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_fromLeft));
+ com.android.internal.R.styleable.ClipRectAnimation_fromLeft), context);
mFromLeftType = d.type;
mFromLeftValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_fromTop));
+ com.android.internal.R.styleable.ClipRectAnimation_fromTop), context);
mFromTopType = d.type;
mFromTopValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_fromRight));
+ com.android.internal.R.styleable.ClipRectAnimation_fromRight), context);
mFromRightType = d.type;
mFromRightValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_fromBottom));
+ com.android.internal.R.styleable.ClipRectAnimation_fromBottom), context);
mFromBottomType = d.type;
mFromBottomValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_toLeft));
+ com.android.internal.R.styleable.ClipRectAnimation_toLeft), context);
mToLeftType = d.type;
mToLeftValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_toTop));
+ com.android.internal.R.styleable.ClipRectAnimation_toTop), context);
mToTopType = d.type;
mToTopValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_toRight));
+ com.android.internal.R.styleable.ClipRectAnimation_toRight), context);
mToRightType = d.type;
mToRightValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ClipRectAnimation_toBottom));
+ com.android.internal.R.styleable.ClipRectAnimation_toBottom), context);
mToBottomType = d.type;
mToBottomValue = d.value;
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
new file mode 100644
index 0000000..210eb8a
--- /dev/null
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Insets;
+import android.util.AttributeSet;
+
+/**
+ * An animation that controls the outset of an object.
+ *
+ * @hide
+ */
+public class ExtendAnimation extends Animation {
+ protected Insets mFromInsets = Insets.NONE;
+ protected Insets mToInsets = Insets.NONE;
+
+ private int mFromLeftType = ABSOLUTE;
+ private int mFromTopType = ABSOLUTE;
+ private int mFromRightType = ABSOLUTE;
+ private int mFromBottomType = ABSOLUTE;
+
+ private int mToLeftType = ABSOLUTE;
+ private int mToTopType = ABSOLUTE;
+ private int mToRightType = ABSOLUTE;
+ private int mToBottomType = ABSOLUTE;
+
+ private float mFromLeftValue;
+ private float mFromTopValue;
+ private float mFromRightValue;
+ private float mFromBottomValue;
+
+ private float mToLeftValue;
+ private float mToTopValue;
+ private float mToRightValue;
+ private float mToBottomValue;
+
+ /**
+ * Constructor used when an ExtendAnimation is loaded from a resource.
+ *
+ * @param context Application context to use
+ * @param attrs Attribute set from which to read values
+ */
+ public ExtendAnimation(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExtendAnimation);
+
+ Description d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft), context);
+ mFromLeftType = d.type;
+ mFromLeftValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendTop), context);
+ mFromTopType = d.type;
+ mFromTopValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendRight), context);
+ mFromRightType = d.type;
+ mFromRightValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom), context);
+ mFromBottomType = d.type;
+ mFromBottomValue = d.value;
+
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendLeft), context);
+ mToLeftType = d.type;
+ mToLeftValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendTop), context);
+ mToTopType = d.type;
+ mToTopValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendRight), context);
+ mToRightType = d.type;
+ mToRightValue = d.value;
+
+ d = Description.parseValue(a.peekValue(
+ com.android.internal.R.styleable.ExtendAnimation_toExtendBottom), context);
+ mToBottomType = d.type;
+ mToBottomValue = d.value;
+
+ a.recycle();
+ }
+
+ /**
+ * Constructor to use when building an ExtendAnimation from code
+ *
+ * @param fromInsets the insets to animate from
+ * @param toInsets the insets to animate to
+ */
+ public ExtendAnimation(Insets fromInsets, Insets toInsets) {
+ if (fromInsets == null || toInsets == null) {
+ throw new RuntimeException("Expected non-null animation outsets");
+ }
+ mFromLeftValue = -fromInsets.left;
+ mFromTopValue = -fromInsets.top;
+ mFromRightValue = -fromInsets.right;
+ mFromBottomValue = -fromInsets.bottom;
+
+ mToLeftValue = -toInsets.left;
+ mToTopValue = -toInsets.top;
+ mToRightValue = -toInsets.right;
+ mToBottomValue = -toInsets.bottom;
+ }
+
+ /**
+ * Constructor to use when building an ExtendAnimation from code
+ */
+ public ExtendAnimation(int fromL, int fromT, int fromR, int fromB,
+ int toL, int toT, int toR, int toB) {
+ this(Insets.of(-fromL, -fromT, -fromR, -fromB), Insets.of(-toL, -toT, -toR, -toB));
+ }
+
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ int l = mFromInsets.left + (int) ((mToInsets.left - mFromInsets.left) * it);
+ int t = mFromInsets.top + (int) ((mToInsets.top - mFromInsets.top) * it);
+ int r = mFromInsets.right + (int) ((mToInsets.right - mFromInsets.right) * it);
+ int b = mFromInsets.bottom + (int) ((mToInsets.bottom - mFromInsets.bottom) * it);
+ tr.setInsets(l, t, r, b);
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return false;
+ }
+
+ /** @hide */
+ @Override
+ public boolean hasExtension() {
+ return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
+ || mFromInsets.bottom < 0;
+ }
+
+ @Override
+ public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ super.initialize(width, height, parentWidth, parentHeight);
+ // We remove any negative extension (i.e. positive insets) and set those to 0
+ mFromInsets = Insets.min(Insets.of(
+ -(int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth),
+ -(int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight),
+ -(int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth),
+ -(int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight)
+ ), Insets.NONE);
+ mToInsets = Insets.min(Insets.of(
+ -(int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth),
+ -(int) resolveSize(mToTopType, mToTopValue, height, parentHeight),
+ -(int) resolveSize(mToRightType, mToRightValue, width, parentWidth),
+ -(int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight)
+ ), Insets.NONE);
+ }
+}
diff --git a/core/java/android/view/animation/GridLayoutAnimationController.java b/core/java/android/view/animation/GridLayoutAnimationController.java
index 0f189ae..c77f54f 100644
--- a/core/java/android/view/animation/GridLayoutAnimationController.java
+++ b/core/java/android/view/animation/GridLayoutAnimationController.java
@@ -116,10 +116,12 @@
com.android.internal.R.styleable.GridLayoutAnimation);
Animation.Description d = Animation.Description.parseValue(
- a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay));
+ a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay),
+ context);
mColumnDelay = d.value;
d = Animation.Description.parseValue(
- a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay));
+ a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay),
+ context);
mRowDelay = d.value;
//noinspection PointlessBitwiseExpression
mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction,
diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java
index e2b7519..1d56d29 100644
--- a/core/java/android/view/animation/LayoutAnimationController.java
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -106,7 +106,7 @@
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation);
Animation.Description d = Animation.Description.parseValue(
- a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay));
+ a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay), context);
mDelay = d.value;
mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL);
diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java
index 3c325d9..0613cd2 100644
--- a/core/java/android/view/animation/RotateAnimation.java
+++ b/core/java/android/view/animation/RotateAnimation.java
@@ -56,12 +56,12 @@
mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f);
Description d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.RotateAnimation_pivotX));
+ com.android.internal.R.styleable.RotateAnimation_pivotX), context);
mPivotXType = d.type;
mPivotXValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.RotateAnimation_pivotY));
+ com.android.internal.R.styleable.RotateAnimation_pivotY), context);
mPivotYType = d.type;
mPivotYValue = d.value;
diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java
index e9a8436..533ef45 100644
--- a/core/java/android/view/animation/ScaleAnimation.java
+++ b/core/java/android/view/animation/ScaleAnimation.java
@@ -118,12 +118,12 @@
}
Description d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ScaleAnimation_pivotX));
+ com.android.internal.R.styleable.ScaleAnimation_pivotX), context);
mPivotXType = d.type;
mPivotXValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.ScaleAnimation_pivotY));
+ com.android.internal.R.styleable.ScaleAnimation_pivotY), context);
mPivotYType = d.type;
mPivotYValue = d.value;
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index b35a66e..bd62308 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -18,6 +18,7 @@
import android.annotation.FloatRange;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -53,6 +54,8 @@
private boolean mHasClipRect;
private Rect mClipRect = new Rect();
+ private Insets mInsets = Insets.NONE;
+
/**
* Creates a new transformation with alpha = 1 and the identity matrix.
*/
@@ -132,8 +135,9 @@
setClipRect(bounds);
}
}
+ setInsets(Insets.add(getInsets(), t.getInsets()));
}
-
+
/**
* Like {@link #compose(Transformation)} but does this.postConcat(t) of
* the transformation matrix.
@@ -160,7 +164,7 @@
public Matrix getMatrix() {
return mMatrix;
}
-
+
/**
* Sets the degree of transparency
* @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
@@ -170,6 +174,13 @@
}
/**
+ * @return The degree of transparency
+ */
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ /**
* Sets the current Transform's clip rect
* @hide
*/
@@ -203,12 +214,29 @@
}
/**
- * @return The degree of transparency
+ * Sets the current Transform's insets
+ * @hide
*/
- public float getAlpha() {
- return mAlpha;
+ public void setInsets(Insets insets) {
+ mInsets = insets;
}
-
+
+ /**
+ * Sets the current Transform's insets
+ * @hide
+ */
+ public void setInsets(int left, int top, int right, int bottom) {
+ mInsets = Insets.of(left, top, right, bottom);
+ }
+
+ /**
+ * Returns the current Transform's outset rect
+ * @hide
+ */
+ public Insets getInsets() {
+ return mInsets;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
@@ -216,7 +244,7 @@
toShortString(sb);
return sb.toString();
}
-
+
/**
* Return a string representation of the transformation in a compact form.
*/
@@ -225,7 +253,7 @@
toShortString(sb);
return sb.toString();
}
-
+
/**
* @hide
*/
@@ -234,7 +262,7 @@
sb.append(" matrix="); sb.append(mMatrix.toShortString());
sb.append('}');
}
-
+
/**
* Print short string, to optimize dumping.
* @hide
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index 3365c70..e27469c 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -73,22 +73,22 @@
com.android.internal.R.styleable.TranslateAnimation);
Description d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.TranslateAnimation_fromXDelta));
+ com.android.internal.R.styleable.TranslateAnimation_fromXDelta), context);
mFromXType = d.type;
mFromXValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.TranslateAnimation_toXDelta));
+ com.android.internal.R.styleable.TranslateAnimation_toXDelta), context);
mToXType = d.type;
mToXValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.TranslateAnimation_fromYDelta));
+ com.android.internal.R.styleable.TranslateAnimation_fromYDelta), context);
mFromYType = d.type;
mFromYValue = d.value;
d = Description.parseValue(a.peekValue(
- com.android.internal.R.styleable.TranslateAnimation_toYDelta));
+ com.android.internal.R.styleable.TranslateAnimation_toYDelta), context);
mToYType = d.type;
mToYValue = d.value;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bb13c1e..60ccf67 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,6 +16,7 @@
package android.view.autofill;
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -538,6 +539,8 @@
*/
public static final int NO_SESSION = Integer.MAX_VALUE;
+ private static final boolean HAS_FILL_DIALOG_UI_FEATURE = false;
+
private final IAutoFillManager mService;
private final Object mLock = new Object();
@@ -629,6 +632,29 @@
@GuardedBy("mLock")
@Nullable private Executor mRequestCallbackExecutor;
+ /**
+ * Indicates whether there are any fields that need to do a fill request
+ * after the activity starts.
+ *
+ * Note: This field will be set to true multiple times if there are many
+ * autofillable views. So needs to check mIsFillRequested at the same time to
+ * avoid re-trigger autofill.
+ */
+ private boolean mRequireAutofill;
+
+ /**
+ * Indicates whether there is already a field to do a fill request after
+ * the activity started.
+ *
+ * Autofill will automatically trigger a fill request after activity
+ * start if there is any field is autofillable. But if there is a field that
+ * triggered autofill, it is unnecessary to trigger again through
+ * AutofillManager#notifyViewEnteredForActivityStarted.
+ */
+ private boolean mIsFillRequested;
+
+ @Nullable private List<AutofillId> mFillDialogTriggerIds;
+
/** @hide */
public interface AutofillClient {
/**
@@ -766,6 +792,8 @@
mContext = Objects.requireNonNull(context, "context cannot be null");
mService = service;
mOptions = context.getAutofillOptions();
+ mIsFillRequested = false;
+ mRequireAutofill = false;
if (mOptions != null) {
sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0;
@@ -1042,6 +1070,39 @@
notifyViewEntered(view, 0);
}
+ /**
+ * The view is autofillable, marked to perform a fill request after layout if
+ * the field does not trigger a fill request.
+ *
+ * @hide
+ */
+ public void enableFillRequestActivityStarted() {
+ mRequireAutofill = true;
+ }
+
+ private boolean hasFillDialogUiFeature() {
+ return HAS_FILL_DIALOG_UI_FEATURE;
+ }
+
+ /**
+ * Notify autofill to do a fill request while the activity started.
+ *
+ * @hide
+ */
+ public void notifyViewEnteredForActivityStarted(@NonNull View view) {
+ if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
+ return;
+ }
+
+ if (!mRequireAutofill || mIsFillRequested) {
+ return;
+ }
+
+ int flags = FLAG_ACTIVITY_START;
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ notifyViewEntered(view, flags);
+ }
+
@GuardedBy("mLock")
private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) {
if (isDisabledByServiceLocked()) {
@@ -1082,6 +1143,7 @@
}
AutofillCallback callback;
synchronized (mLock) {
+ mIsFillRequested = true;
callback = notifyViewEnteredLocked(view, flags);
}
@@ -2026,6 +2088,8 @@
mFillableIds = null;
mSaveTriggerId = null;
mIdShownFillUi = null;
+ mIsFillRequested = false;
+ mRequireAutofill = false;
if (resetEnteredIds) {
mEnteredIds = null;
}
@@ -3031,6 +3095,29 @@
client.autofillClientRunOnUiThread(runnable);
}
+ private void setFillDialogTriggerIds(@Nullable List<AutofillId> ids) {
+ mFillDialogTriggerIds = ids;
+ }
+
+ /**
+ * Checks the id of autofill whether supported the fill dialog.
+ *
+ * @hide
+ */
+ public boolean isShowFillDialog(AutofillId id) {
+ if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) {
+ return false;
+ }
+ final int size = mFillDialogTriggerIds.size();
+ for (int i = 0; i < size; i++) {
+ AutofillId fillId = mFillDialogTriggerIds.get(i);
+ if (fillId.equalsIgnoreSession(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Implementation of the accessibility based compatibility.
*/
@@ -3736,6 +3823,13 @@
new FillCallback(callback, id));
}
}
+
+ public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.setFillDialogTriggerIds(ids));
+ }
+ }
}
private static final class AugmentedAutofillManagerClient
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 64507aa..2e5967c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -148,4 +148,9 @@
*/
void requestFillFromClient(int id, in InlineSuggestionsRequest request,
in IFillCallback callback);
+
+ /**
+ * Notifies autofill ids that require to show the fill dialog.
+ */
+ void notifyFillDialogTriggerIds(in List<AutofillId> ids);
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 9552691..2134d81 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -291,6 +291,8 @@
* <p>Typically used to change the context associated with the default session from an activity.
*/
public final void setContentCaptureContext(@Nullable ContentCaptureContext context) {
+ if (!isContentCaptureEnabled()) return;
+
mClientContext = context;
updateContentCaptureContext(context);
}
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..08cc31c 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -105,12 +105,14 @@
* current IME.
* @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
* @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
* @hide
*/
@MainThread
default void initializeInternal(IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported) {
+ boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) {
attachToken(token);
}
@@ -229,6 +231,8 @@
* the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
* long as your implementation of {@link InputMethod} relies on such
* IPCs
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
* @see #startInput(InputConnection, EditorInfo)
* @see #restartInput(InputConnection, EditorInfo)
* @see EditorInfo
@@ -237,7 +241,7 @@
@MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) {
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
@@ -246,6 +250,18 @@
}
/**
+ * Notifies that whether the IME should show the IME switcher or not is being changed.
+ *
+ * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be
+ * shown while the IME is shown.
+ * @hide
+ */
+ @MainThread
+ default void onShouldShowImeSwitcherWhenImeIsShownChanged(
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
+ }
+
+ /**
* Create a new {@link InputMethodSession} that can be handed to client
* applications for interacting with the input method. You can later
* use {@link #revokeSession(InputMethodSession)} to destroy the session
@@ -405,7 +421,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.aidl b/core/java/android/view/selectiontoolbar/SelectionContext.aidl
deleted file mode 100644
index 5206831..0000000
--- a/core/java/android/view/selectiontoolbar/SelectionContext.aidl
+++ /dev/null
@@ -1,22 +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;
-
-/**
- * @hide
- */
-parcelable SelectionContext;
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/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c6f64f4..b00a382 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -301,6 +301,13 @@
public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
/**
+ * This mask determines which flags are propagated to nested RemoteViews (either added by
+ * addView, or set as portrait/landscape/sized RemoteViews).
+ */
+ static final int FLAG_MASK_TO_PROPAGATE =
+ FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
+
+ /**
* A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is
* intentionally a different instance in order to trick Bundle reader so that it doesn't allow
* lazy initialization.
@@ -467,6 +474,18 @@
*/
public void addFlags(@ApplyFlags int flags) {
mApplyFlags = mApplyFlags | flags;
+
+ int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) {
+ if (hasSizedRemoteViews()) {
+ for (RemoteViews remoteView : mSizedRemoteViews) {
+ remoteView.addFlags(flagsToPropagate);
+ }
+ } else if (hasLandscapeAndPortraitLayouts()) {
+ mLandscape.addFlags(flagsToPropagate);
+ mPortrait.addFlags(flagsToPropagate);
+ }
+ }
}
/**
@@ -2407,6 +2426,10 @@
// will return -1.
final int nextChild = getNextRecyclableChild(target);
RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
+
+ int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE;
+ if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate);
+
if (nextChild >= 0 && mStableId != NO_ID) {
// At that point, the views starting at index nextChild are the ones recyclable but
// not yet recycled. All views added on that round of application are placed before.
@@ -2419,8 +2442,8 @@
target.removeViews(nextChild, recycledViewIndex - nextChild);
}
setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
- rvToApply.reapply(context, child, handler, null /* size */, colorResources,
- false /* topLevel */);
+ rvToApply.reapplyNestedViews(context, child, rootParent, handler,
+ null /* size */, colorResources);
return;
}
// If we cannot recycle the views, we still remove all views in between to
@@ -2431,8 +2454,8 @@
// If we cannot recycle, insert the new view before the next recyclable child.
// Inflate nested views and add as children
- View nestedView = rvToApply.apply(context, target, handler, null /* size */,
- colorResources);
+ View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler,
+ null /* size */, colorResources);
if (mStableId != NO_ID) {
setStableId(nestedView, mStableId);
}
@@ -3780,7 +3803,7 @@
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
+ this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
}
private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
@@ -5580,6 +5603,16 @@
return result;
}
+ private View applyNestedViews(Context context, ViewGroup directParent,
+ ViewGroup rootParent, InteractionHandler handler, SizeF size,
+ ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+ View result = inflateView(context, rvToApply, directParent, 0, colorResources);
+ rvToApply.performApply(result, rootParent, handler, colorResources);
+ return result;
+ }
+
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
return inflateView(context, rv, parent, 0, null);
}
@@ -5895,6 +5928,12 @@
}
}
+ private void reapplyNestedViews(Context context, View v, ViewGroup rootParent,
+ InteractionHandler handler, SizeF size, ColorResources colorResources) {
+ RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size);
+ rvToApply.performApply(v, rootParent, handler, colorResources);
+ }
+
/**
* Applies all the actions to the provided view, moving as much of the task on the background
* thread as possible.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 41c5401..0fe2ed5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4461,7 +4461,7 @@
* pixel" units. This size is adjusted based on the current density and
* user font size preference.
*
- * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+ * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
*
* @param size The scaled pixel size.
*
@@ -4476,7 +4476,7 @@
* Set the default text size to a given unit and value. See {@link
* TypedValue} for the possible dimension units.
*
- * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
+ * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op.
*
* @param unit The desired dimension unit.
* @param size The desired size in the given units.
@@ -12289,6 +12289,7 @@
EXTRA_DATA_RENDERING_INFO_KEY,
EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
));
+ info.setTextSelectable(isTextSelectable());
} else {
info.setAvailableExtraData(Arrays.asList(
EXTRA_DATA_RENDERING_INFO_KEY
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 571714c..18c20e2 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -16,8 +16,6 @@
package android.window;
-import static java.util.Objects.requireNonNull;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -61,6 +59,12 @@
public static final int TYPE_CROSS_TASK = 3;
/**
+ * A {@link android.view.OnBackInvokedCallback} is available and needs to be called.
+ * <p>
+ */
+ public static final int TYPE_CALLBACK = 4;
+
+ /**
* Defines the type of back destinations a back even can lead to. This is used to define the
* type of animation that need to be run on SystemUI.
*/
@@ -84,35 +88,39 @@
private final RemoteCallback mRemoteCallback;
@Nullable
private final WindowConfiguration mTaskWindowConfiguration;
+ @Nullable
+ private final IOnBackInvokedCallback mOnBackInvokedCallback;
/**
* Create a new {@link BackNavigationInfo} instance.
*
- * @param type The {@link BackTargetType} of the destination (what will be displayed after
- * the back action)
- * @param topWindowLeash The leash to animate away the current topWindow. The consumer
- * of the leash is responsible for removing it.
- * @param screenshotSurface The screenshot of the previous activity to be displayed.
- * @param screenshotBuffer A buffer containing a screenshot used to display the activity.
- * See {@link #getScreenshotHardwareBuffer()} for information
- * about nullity.
- * @param taskWindowConfiguration The window configuration of the Task being animated
- * beneath.
- * @param onBackNavigationDone The callback to be called once the client is done with the back
- * preview.
+ * @param type The {@link BackTargetType} of the destination (what will be
+ * displayed after the back action).
+ * @param topWindowLeash The leash to animate away the current topWindow. The consumer
+ * of the leash is responsible for removing it.
+ * @param screenshotSurface The screenshot of the previous activity to be displayed.
+ * @param screenshotBuffer A buffer containing a screenshot used to display the activity.
+ * See {@link #getScreenshotHardwareBuffer()} for information
+ * about nullity.
+ * @param taskWindowConfiguration The window configuration of the Task being animated beneath.
+ * @param onBackNavigationDone The callback to be called once the client is done with the
+ * back preview.
+ * @param onBackInvokedCallback The back callback registered by the current top level window.
*/
public BackNavigationInfo(@BackTargetType int type,
@Nullable SurfaceControl topWindowLeash,
@Nullable SurfaceControl screenshotSurface,
@Nullable HardwareBuffer screenshotBuffer,
@Nullable WindowConfiguration taskWindowConfiguration,
- @NonNull RemoteCallback onBackNavigationDone) {
+ @Nullable RemoteCallback onBackNavigationDone,
+ @Nullable IOnBackInvokedCallback onBackInvokedCallback) {
mType = type;
mDepartingWindowContainer = topWindowLeash;
mScreenshotSurface = screenshotSurface;
mScreenshotBuffer = screenshotBuffer;
mTaskWindowConfiguration = taskWindowConfiguration;
mRemoteCallback = onBackNavigationDone;
+ mOnBackInvokedCallback = onBackInvokedCallback;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -121,7 +129,8 @@
mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR);
mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR);
mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
- mRemoteCallback = requireNonNull(in.readTypedObject(RemoteCallback.CREATOR));
+ mRemoteCallback = in.readTypedObject(RemoteCallback.CREATOR);
+ mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
}
@Override
@@ -132,10 +141,12 @@
dest.writeTypedObject(mScreenshotBuffer, flags);
dest.writeTypedObject(mTaskWindowConfiguration, flags);
dest.writeTypedObject(mRemoteCallback, flags);
+ dest.writeStrongInterface(mOnBackInvokedCallback);
}
/**
* Returns the type of back navigation that is about to happen.
+ *
* @see BackTargetType
*/
public @BackTargetType int getType() {
@@ -152,8 +163,8 @@
}
/**
- * Returns the {@link SurfaceControl} that should be used to display a screenshot of the
- * previous activity.
+ * Returns the {@link SurfaceControl} that should be used to display a screenshot of the
+ * previous activity.
*/
@Nullable
public SurfaceControl getScreenshotSurface() {
@@ -185,11 +196,27 @@
}
/**
+ * Returns the {@link android.view.OnBackInvokedCallback} of the top level window or null if
+ * the client didn't register a callback.
+ * <p>
+ * This is never null when {@link #getType} returns {@link #TYPE_CALLBACK}.
+ *
+ * @see android.view.OnBackInvokedCallback
+ * @see android.view.OnBackInvokedDispatcher
+ */
+ @Nullable
+ public IOnBackInvokedCallback getOnBackInvokedCallback() {
+ return mOnBackInvokedCallback;
+ }
+
+ /**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
*/
public void onBackNavigationFinished() {
- mRemoteCallback.sendResult(null);
+ if (mRemoteCallback != null) {
+ mRemoteCallback.sendResult(null);
+ }
}
@Override
@@ -218,6 +245,7 @@
+ ", mTaskWindowConfiguration= " + mTaskWindowConfiguration
+ ", mScreenshotBuffer=" + mScreenshotBuffer
+ ", mRemoteCallback=" + mRemoteCallback
+ + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+ '}';
}
@@ -226,7 +254,7 @@
*/
public static String typeToString(@BackTargetType int type) {
switch (type) {
- case TYPE_UNDEFINED:
+ case TYPE_UNDEFINED:
return "TYPE_UNDEFINED";
case TYPE_DIALOG_CLOSE:
return "TYPE_DIALOG_CLOSE";
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/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
new file mode 100644
index 0000000..509bbd4
--- /dev/null
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+import android.view.OnBackInvokedCallback;
+import android.view.OnBackInvokedDispatcher;
+import android.view.OnBackInvokedDispatcherOwner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link OnBackInvokedDispatcher} only used to hold callbacks while an actual
+ * dispatcher becomes available. <b>It does not dispatch the back events</b>.
+ * <p>
+ * Once the actual {@link OnBackInvokedDispatcherOwner} becomes available,
+ * {@link #setActualDispatcherOwner(OnBackInvokedDispatcherOwner)} needs to
+ * be called and this {@link ProxyOnBackInvokedDispatcher} will pass the callback registrations
+ * onto it.
+ * <p>
+ * This dispatcher will continue to keep track of callback registrations and when a dispatcher is
+ * removed or set it will unregister the callbacks from the old one and register them on the new
+ * one unless {@link #reset()} is called before.
+ *
+ * @hide
+ */
+public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
+
+ /**
+ * List of pair representing an {@link OnBackInvokedCallback} and its associated priority.
+ *
+ * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(OnBackInvokedCallback, int)
+ */
+ private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>();
+ private final Object mLock = new Object();
+ private OnBackInvokedDispatcherOwner mActualDispatcherOwner = null;
+
+ @Override
+ public void registerOnBackInvokedCallback(
+ @NonNull OnBackInvokedCallback callback, int priority) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("Pending register %s. Actual=%s", callback,
+ mActualDispatcherOwner));
+ }
+ synchronized (mLock) {
+ mCallbacks.add(Pair.create(callback, priority));
+ if (mActualDispatcherOwner != null) {
+ mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ callback, priority);
+ }
+
+ }
+ }
+
+ @Override
+ public void unregisterOnBackInvokedCallback(
+ @NonNull OnBackInvokedCallback callback) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("Pending unregister %s. Actual=%s", callback,
+ mActualDispatcherOwner));
+ }
+ synchronized (mLock) {
+ mCallbacks.removeIf((p) -> p.first.equals(callback));
+ }
+ }
+
+ /**
+ * Transfers all the pending callbacks to the provided dispatcher.
+ * <p>
+ * The callbacks are registered on the dispatcher in the same order as they were added on this
+ * proxy dispatcher.
+ */
+ private void transferCallbacksToDispatcher() {
+ if (mActualDispatcherOwner == null) {
+ return;
+ }
+ OnBackInvokedDispatcher dispatcher =
+ mActualDispatcherOwner.getOnBackInvokedDispatcher();
+ if (DEBUG) {
+ Log.v(TAG, String.format("Pending transferring %d callbacks to %s", mCallbacks.size(),
+ dispatcher));
+ }
+ for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
+ dispatcher.registerOnBackInvokedCallback(callbackPair.first,
+ callbackPair.second);
+ }
+ mCallbacks.clear();
+ }
+
+ private void clearCallbacksOnDispatcher() {
+ if (mActualDispatcherOwner == null) {
+ return;
+ }
+ OnBackInvokedDispatcher onBackInvokedDispatcher =
+ mActualDispatcherOwner.getOnBackInvokedDispatcher();
+ for (Pair<OnBackInvokedCallback, Integer> callback : mCallbacks) {
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(callback.first);
+ }
+ }
+
+ /**
+ * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently
+ * registered callbacks.
+ * <p>
+ * Using this method means that when setting a new {@link OnBackInvokedDispatcherOwner}, the
+ * callbacks registered on the old one won't be removed from it and won't be registered on
+ * the new one.
+ */
+ public void reset() {
+ if (DEBUG) {
+ Log.v(TAG, "Pending reset callbacks");
+ }
+ synchronized (mLock) {
+ mCallbacks.clear();
+ }
+ }
+
+ /**
+ * Sets the actual {@link OnBackInvokedDispatcherOwner} that will provides the
+ * {@link OnBackInvokedDispatcher} onto which the callbacks will be registered.
+ * <p>
+ * If any dispatcher owner was already present, all the callbacks that were added via this
+ * {@link ProxyOnBackInvokedDispatcher} will be unregistered from the old one and registered
+ * on the new one if it is not null.
+ * <p>
+ * If you do not wish for the previously registered callbacks to be reassigned to the new
+ * dispatcher, {@link #reset} must be called beforehand.
+ */
+ public void setActualDispatcherOwner(
+ @Nullable OnBackInvokedDispatcherOwner actualDispatcherOwner) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("Pending setActual %s. Current %s",
+ actualDispatcherOwner, mActualDispatcherOwner));
+ }
+ synchronized (mLock) {
+ if (actualDispatcherOwner == mActualDispatcherOwner) {
+ return;
+ }
+ clearCallbacksOnDispatcher();
+ mActualDispatcherOwner = actualDispatcherOwner;
+ transferCallbacksToDispatcher();
+ }
+ }
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index fd1e848..3fa62e0 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -599,6 +599,7 @@
private final Rect mTransitionBounds = new Rect();
private HardwareBuffer mThumbnail;
private int mAnimations;
+ private @ColorInt int mBackgroundColor;
private AnimationOptions(int type) {
mType = type;
@@ -608,6 +609,7 @@
mType = in.readInt();
mEnterResId = in.readInt();
mExitResId = in.readInt();
+ mBackgroundColor = in.readInt();
mOverrideTaskTransition = in.readBoolean();
mPackageName = in.readString();
mTransitionBounds.readFromParcel(in);
@@ -624,11 +626,12 @@
}
public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
- int exitResId, boolean overrideTaskTransition) {
+ int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
options.mPackageName = packageName;
options.mEnterResId = enterResId;
options.mExitResId = exitResId;
+ options.mBackgroundColor = backgroundColor;
options.mOverrideTaskTransition = overrideTaskTransition;
return options;
}
@@ -673,6 +676,10 @@
return mExitResId;
}
+ public @ColorInt int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
public boolean getOverrideTaskTransition() {
return mOverrideTaskTransition;
}
@@ -698,6 +705,7 @@
dest.writeInt(mType);
dest.writeInt(mEnterResId);
dest.writeInt(mExitResId);
+ dest.writeInt(mBackgroundColor);
dest.writeBoolean(mOverrideTaskTransition);
dest.writeString(mPackageName);
mTransitionBounds.writeToParcel(dest, flags);
@@ -740,7 +748,7 @@
@Override
public String toString() {
- return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+ return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
+ " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
}
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/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 9648008..629a1b3 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -113,7 +113,7 @@
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
- void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType);
+ void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency);
void notePhoneState(int phoneState);
void noteWifiOn();
void noteWifiOff();
@@ -145,6 +145,8 @@
long getAwakeTimeBattery();
long getAwakeTimePlugged();
+ void noteBluetoothOn(int uid, int reason, String packageName);
+ void noteBluetoothOff(int uid, int reason, String packageName);
void noteBleScanStarted(in WorkSource ws, boolean isUnoptimized);
void noteBleScanStopped(in WorkSource ws, boolean isUnoptimized);
void noteBleScanReset();
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/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 347153c..cdb69e5 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1011,7 +1011,9 @@
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ViewPager viewPager = findViewById(R.id.profile_pager);
- outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+ if (viewPager != null) {
+ outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+ }
}
@Override
@@ -1019,7 +1021,9 @@
super.onRestoreInstanceState(savedInstanceState);
resetButtonBar();
ViewPager viewPager = findViewById(R.id.profile_pager);
- viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+ if (viewPager != null) {
+ viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+ }
mMultiProfilePagerAdapter.clearInactiveProfileCache();
}
@@ -1568,6 +1572,11 @@
rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
}
+ if (shouldUseMiniResolver()) {
+ configureMiniResolverContent();
+ return false;
+ }
+
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
@@ -1578,6 +1587,72 @@
return postRebuildList(rebuildCompleted);
}
+ private void configureMiniResolverContent() {
+ mLayoutId = R.layout.miniresolver;
+ setContentView(mLayoutId);
+
+ DisplayResolveInfo sameProfileResolveInfo =
+ mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
+ boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
+
+ DisplayResolveInfo otherProfileResolveInfo =
+ mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList.get(0);
+ ImageView icon = findViewById(R.id.icon);
+ // TODO: Set icon drawable to app icon.
+
+ ((TextView) findViewById(R.id.open_cross_profile)).setText(
+ getResources().getString(
+ inWorkProfile ? R.string.miniresolver_open_in_personal
+ : R.string.miniresolver_open_in_work,
+ otherProfileResolveInfo.getDisplayLabel()));
+ ((Button) findViewById(R.id.use_same_profile_browser)).setText(
+ inWorkProfile ? R.string.miniresolver_use_work_browser
+ : R.string.miniresolver_use_personal_browser);
+
+ findViewById(R.id.use_same_profile_browser).setOnClickListener(
+ v -> safelyStartActivity(sameProfileResolveInfo));
+
+ findViewById(R.id.button_open).setOnClickListener(v -> {
+ Intent intent = otherProfileResolveInfo.getResolvedIntent();
+ if (intent != null) {
+ prepareIntentForCrossProfileLaunch(intent);
+ }
+ safelyStartActivityInternal(otherProfileResolveInfo,
+ mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController
+ .getUserHandle());
+ });
+ }
+
+ private boolean shouldUseMiniResolver() {
+ if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
+ || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ return false;
+ }
+ List<DisplayResolveInfo> sameProfileList =
+ mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList;
+ List<DisplayResolveInfo> otherProfileList =
+ mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList;
+
+ if (otherProfileList.size() != 1) {
+ Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile");
+ return false;
+ }
+
+ if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) {
+ Log.d(TAG, "Other profile is a web browser");
+ return false;
+ }
+
+ for (DisplayResolveInfo info : sameProfileList) {
+ if (!info.getResolveInfo().handleAllWebDataURI) {
+ Log.d(TAG, "Non-browser found in this profile");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Finishing procedures to be performed after the list has been rebuilt.
* </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/InstallLocationUtils.java
similarity index 87%
rename from core/java/com/android/internal/content/PackageHelper.java
rename to core/java/com/android/internal/content/InstallLocationUtils.java
index c2f2052..c456cf3 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.content;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
import android.content.Context;
@@ -53,7 +54,7 @@
* and media container service transports.
* Some utility methods to invoke StorageManagerService api.
*/
-public class PackageHelper {
+public class InstallLocationUtils {
public static final int RECOMMEND_INSTALL_INTERNAL = 1;
public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
public static final int RECOMMEND_INSTALL_EPHEMERAL = 3;
@@ -89,9 +90,13 @@
*/
public static abstract class TestableInterface {
abstract public StorageManager getStorageManager(Context context);
+
abstract public boolean getForceAllowOnExternalSetting(Context context);
+
abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
+
abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
+
abstract public File getDataDirectory();
}
@@ -150,11 +155,11 @@
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual volume to install the app. Only considers
- * internal and private volumes, and prefers to keep an existing package on
+ * internal and private volumes, and prefers to keep an existing package onocation
* its current volume.
*
* @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
- * for internal storage.
+ * for internal storage.
*/
public static String resolveInstallVolume(Context context, SessionParams params)
throws IOException {
@@ -316,21 +321,6 @@
&& params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
}
- @Deprecated
- public static int resolveInstallLocation(Context context, String packageName,
- int installLocation, long sizeBytes, int installFlags) {
- final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
- params.appPackageName = packageName;
- params.installLocation = installLocation;
- params.sizeBytes = sizeBytes;
- params.installFlags = installFlags;
- try {
- return resolveInstallLocation(context, params);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
/**
* Given a requested {@link PackageInfo#installLocation} and calculated
* install size, pick the actual location to install the app.
@@ -393,24 +383,24 @@
// and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {
return (ephemeral)
- ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
- : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL
+ : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
}
if (checkBoth) {
if (fitsOnInternal) {
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else if (fitsOnExternal) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
}
- return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
@Deprecated
@@ -476,4 +466,48 @@
return 0;
}
}
+
+ public static int installLocationPolicy(int installLocation, int recommendedInstallLocation,
+ int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) {
+ if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+ // Invalid install. Return error code
+ return RECOMMEND_FAILED_ALREADY_EXISTS;
+ }
+ // Check for updated system application.
+ if (installedPkgIsSystem) {
+ return RECOMMEND_INSTALL_INTERNAL;
+ }
+ // If current upgrade specifies particular preference
+ if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ // Application explicitly specified internal.
+ return RECOMMEND_INSTALL_INTERNAL;
+ } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+ // App explicitly prefers external. Let policy decide
+ return recommendedInstallLocation;
+ } else {
+ // Prefer previous location
+ if (installedPackageOnExternal) {
+ return RECOMMEND_INSTALL_EXTERNAL;
+ }
+ return RECOMMEND_INSTALL_INTERNAL;
+ }
+ }
+
+ public static int getInstallationErrorCode(int loc) {
+ if (loc == RECOMMEND_FAILED_INVALID_LOCATION) {
+ return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) {
+ return PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+ } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ } else if (loc == RECOMMEND_FAILED_INVALID_APK) {
+ return PackageManager.INSTALL_FAILED_INVALID_APK;
+ } else if (loc == RECOMMEND_FAILED_INVALID_URI) {
+ return PackageManager.INSTALL_FAILED_INVALID_URI;
+ } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) {
+ return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+ } else {
+ return INSTALL_SUCCEEDED;
+ }
+ }
}
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/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/core/java/com/android/internal/logging/InstanceId.aidl
similarity index 81%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to core/java/com/android/internal/logging/InstanceId.aidl
index cb602d79..19a6177 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/core/java/com/android/internal/logging/InstanceId.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,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package com.android.internal.logging;
-parcelable NetworkStateSnapshot;
+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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 25ee2d0..5ba45c9 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -69,10 +69,14 @@
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.connectivity.WifiBatteryStats;
import android.provider.Settings;
+import android.telephony.Annotation.NetworkType;
import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -603,11 +607,15 @@
int UPDATE_BT = 0x08;
int UPDATE_RPM = 0x10;
int UPDATE_DISPLAY = 0x20;
+ int RESET = 0x40;
+
int UPDATE_ALL =
UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY;
int UPDATE_ON_PROC_STATE_CHANGE = UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
+ int UPDATE_ON_RESET = UPDATE_ALL | RESET;
+
@IntDef(flag = true, prefix = "UPDATE_", value = {
UPDATE_CPU,
UPDATE_WIFI,
@@ -935,6 +943,119 @@
final StopwatchTimer[] mPhoneDataConnectionsTimer =
new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
+ @RadioAccessTechnology
+ int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER;
+
+ private static class RadioAccessTechnologyBatteryStats {
+ /**
+ * This RAT is currently being used.
+ */
+ private boolean mActive = false;
+ /**
+ * Current active frequency range for this RAT.
+ */
+ @ServiceState.FrequencyRange
+ private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ /**
+ * Current signal strength for this RAT.
+ */
+ private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ /**
+ * Timers for each combination of frequency range and signal strength.
+ */
+ public final StopwatchTimer[][] perStateTimers;
+
+ RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
+ perStateTimers =
+ new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS];
+ for (int i = 0; i < freqCount; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase);
+ }
+ }
+ }
+
+ /**
+ * Note this RAT is currently being used.
+ */
+ public void noteActive(boolean active, long elapsedRealtimeMs) {
+ if (mActive == active) return;
+ mActive = active;
+ if (mActive) {
+ perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked(
+ elapsedRealtimeMs);
+ } else {
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ }
+
+ /**
+ * Note current frequency range has changed.
+ */
+ public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange,
+ long elapsedRealtimeMs) {
+ if (mFrequencyRange == frequencyRange) return;
+
+ if (!mActive) {
+ // RAT not in use, note the frequency change and move on.
+ mFrequencyRange = frequencyRange;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs);
+ mFrequencyRange = frequencyRange;
+ }
+
+ /**
+ * Note current signal strength has changed.
+ */
+ public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) {
+ if (mSignalStrength == signalStrength) return;
+
+ if (!mActive) {
+ // RAT not in use, note the signal strength change and move on.
+ mSignalStrength = signalStrength;
+ return;
+ }
+ perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs);
+ perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs);
+ mSignalStrength = signalStrength;
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ final int size = perStateTimers.length;
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+ perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+ }
+
+ /**
+ * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange}
+ */
+ private static final int NR_FREQUENCY_COUNT = 5;
+
+ RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats =
+ new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT];
+
+ @GuardedBy("this")
+ private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked(
+ @RadioAccessTechnology int rat) {
+ RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) {
+ final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1;
+ stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase);
+ mPerRatBatteryStats[rat] = stats;
+ }
+ return stats;
+ }
+
final LongSamplingCounter[] mNetworkByteActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -1896,17 +2017,15 @@
private final TimeBase mTimeBase;
private final LongArrayMultiStateCounter mCounter;
- private TimeInFreqMultiStateCounter(TimeBase timeBase, Parcel in, long timestampMs) {
- mTimeBase = timeBase;
- mCounter = LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
- mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
- timeBase.add(this);
- }
-
private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount,
long timestampMs) {
+ this(timeBase, new LongArrayMultiStateCounter(stateCount, cpuFreqCount), timestampMs);
+ }
+
+ private TimeInFreqMultiStateCounter(TimeBase timeBase, LongArrayMultiStateCounter counter,
+ long timestampMs) {
mTimeBase = timeBase;
- mCounter = new LongArrayMultiStateCounter(stateCount, cpuFreqCount);
+ mCounter = counter;
mCounter.setEnabled(mTimeBase.isRunning(), timestampMs);
timeBase.add(this);
}
@@ -1915,6 +2034,19 @@
mCounter.writeToParcel(out, 0);
}
+ @Nullable
+ private static TimeInFreqMultiStateCounter readFromParcel(Parcel in, TimeBase timeBase,
+ int stateCount, int cpuFreqCount, long timestampMs) {
+ // Read the object from the Parcel, whether it's usable or not
+ LongArrayMultiStateCounter counter =
+ LongArrayMultiStateCounter.CREATOR.createFromParcel(in);
+ if (counter.getStateCount() != stateCount
+ || counter.getArrayLength() != cpuFreqCount) {
+ return null;
+ }
+ return new TimeInFreqMultiStateCounter(timeBase, counter, timestampMs);
+ }
+
@Override
public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) {
mCounter.setEnabled(true, elapsedRealtimeUs / 1000);
@@ -5886,6 +6018,10 @@
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mMobileRadioPowerState = powerState;
+
+ // Inform current RatBatteryStats that the modem active state might have changed.
+ getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs);
+
if (active) {
mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs);
mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs);
@@ -6307,21 +6443,86 @@
@GuardedBy("this")
public void notePhoneSignalStrengthLocked(SignalStrength signalStrength,
long elapsedRealtimeMs, long uptimeMs) {
- // Bin the strength.
- int bin = signalStrength.getLevel();
- updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin,
+ final int overallSignalStrength = signalStrength.getLevel();
+ final SparseIntArray perRatSignalStrength = new SparseIntArray(
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT);
+
+ // Extract signal strength level for each RAT.
+ final List<CellSignalStrength> cellSignalStrengths =
+ signalStrength.getCellSignalStrengths();
+ final int size = cellSignalStrengths.size();
+ for (int i = 0; i < size; i++) {
+ CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i);
+ // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology
+ final int ratType;
+ final int level;
+ if (cellSignalStrength instanceof CellSignalStrengthNr) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_NR;
+ level = cellSignalStrength.getLevel();
+ } else if (cellSignalStrength instanceof CellSignalStrengthLte) {
+ ratType = RADIO_ACCESS_TECHNOLOGY_LTE;
+ level = cellSignalStrength.getLevel();
+ } else {
+ ratType = RADIO_ACCESS_TECHNOLOGY_OTHER;
+ level = cellSignalStrength.getLevel();
+ }
+
+ // According to SignalStrength#getCellSignalStrengths(), multiple of the same
+ // cellSignalStrength can be present. Just take the highest level one for each RAT.
+ if (perRatSignalStrength.get(ratType, -1) < level) {
+ perRatSignalStrength.put(ratType, level);
+ }
+ }
+
+ notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength,
+ elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength) {
+ notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength,
+ mClock.elapsedRealtime(), mClock.uptimeMillis());
+ }
+
+ /**
+ * Note phone signal strength change, including per RAT signal strength.
+ *
+ * @param signalStrength overall signal strength {@see SignalStrength#getLevel()}
+ * @param perRatSignalStrength signal strength of available RATs
+ */
+ @GuardedBy("this")
+ public void notePhoneSignalStrengthLocked(int signalStrength,
+ SparseIntArray perRatSignalStrength,
+ long elapsedRealtimeMs, long uptimeMs) {
+ // Note each RAT's signal strength.
+ final int size = perRatSignalStrength.size();
+ for (int i = 0; i < size; i++) {
+ final int rat = perRatSignalStrength.keyAt(i);
+ final int ratSignalStrength = perRatSignalStrength.valueAt(i);
+ getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs);
+ }
+ updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength,
elapsedRealtimeMs, uptimeMs);
}
@UnsupportedAppUsage
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) {
- notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) {
+ notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency,
mClock.elapsedRealtime(), mClock.uptimeMillis());
}
@GuardedBy("this")
- public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType,
+ public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData,
+ @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency,
long elapsedRealtimeMs, long uptimeMs) {
// BatteryStats uses 0 to represent no network type.
// Telephony does not have a concept of no network type, and uses 0 to represent unknown.
@@ -6344,6 +6545,13 @@
}
}
}
+
+ final int newRat = mapNetworkTypeToRadioAccessTechnology(bin);
+ if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) {
+ // Note possible frequency change for the NR RAT.
+ getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs);
+ }
+
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
@@ -6357,6 +6565,45 @@
}
mPhoneDataConnectionType = bin;
mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs);
+
+ if (mActiveRat != newRat) {
+ getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs);
+ mActiveRat = newRat;
+ }
+ final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked();
+ getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs);
+ }
+ }
+
+ @RadioAccessTechnology
+ private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) {
+ switch (dataType) {
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return RADIO_ACCESS_TECHNOLOGY_NR;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return RADIO_ACCESS_TECHNOLOGY_LTE;
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough
+ case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
+ default:
+ Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER");
+ return RADIO_ACCESS_TECHNOLOGY_OTHER;
}
}
@@ -7731,6 +7978,23 @@
return mPhoneDataConnectionsTimer[dataType];
}
+ @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+ long elapsedRealtimeMs) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+ if (stats == null) return 0L;
+
+ final int freqCount = stats.perStateTimers.length;
+ if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L;
+
+ final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange];
+ final int strengthCount = strengthTimers.length;
+ if (signalStrength < 0 || signalStrength >= strengthCount) return 0L;
+
+ return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked(
+ elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
+ }
+
@UnsupportedAppUsage
@Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -10535,25 +10799,18 @@
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
- mBsi.mOnBatteryTimeBase, in, timestampMs);
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- mProcStateTimeMs = counter;
- }
+ mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mBsi.mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
} else {
mProcStateTimeMs = null;
}
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter =
- new TimeInFreqMultiStateCounter(
- mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- mProcStateScreenOffTimeMs = counter;
- }
+ mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mBsi.mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime());
} else {
mProcStateScreenOffTimeMs = null;
}
@@ -12553,6 +12810,11 @@
mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs);
mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs);
}
+ for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) {
+ final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i];
+ if (stats == null) continue;
+ stats.reset(elapsedRealtimeUs);
+ }
mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs);
mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs);
@@ -12651,7 +12913,7 @@
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
- mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ALL);
+ mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
mHandler.sendEmptyMessage(MSG_REPORT_RESET_STATS);
}
@@ -16107,6 +16369,11 @@
BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
KEY_BATTERY_CHARGED_DELAY_MS,
DEFAULT_BATTERY_CHARGED_DELAY_MS);
+
+ if (mHandler.hasCallbacks(mDeferSetCharging)) {
+ mHandler.removeCallbacks(mDeferSetCharging);
+ mHandler.postDelayed(mDeferSetCharging, BATTERY_CHARGED_DELAY_MS);
+ }
}
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
@@ -16906,12 +17173,10 @@
stateCount = in.readInt();
if (stateCount != 0) {
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
- mOnBatteryTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- u.mProcStateTimeMs = counter;
- }
+ detachIfNotNull(u.mProcStateTimeMs);
+ u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ getCpuFreqCount(), mClock.elapsedRealtime());
}
detachIfNotNull(u.mProcStateScreenOffTimeMs);
@@ -16920,13 +17185,9 @@
stateCount = in.readInt();
if (stateCount != 0) {
detachIfNotNull(u.mProcStateScreenOffTimeMs);
- // Read the object from the Parcel, whether it's usable or not
- TimeInFreqMultiStateCounter counter =
- new TimeInFreqMultiStateCounter(
- mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
- u.mProcStateScreenOffTimeMs = counter;
- }
+ u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in,
+ mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT,
+ getCpuFreqCount(), mClock.elapsedRealtime());
}
if (in.readInt() != 0) {
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/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index be91aac..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1159,6 +1159,17 @@
: Integer.compare(a.transactionCode, b.transactionCode);
}
+ /** @hide */
+ public static void startForBluetooth(Context context) {
+ new BinderCallsStats.SettingsObserver(
+ context,
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
+ }
+
+
/**
* Settings observer for other processes (not system_server).
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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
similarity index 61%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index 861a4ed..9be686a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package com.android.internal.os;
-parcelable DeviceInfo;
+/**
+ * "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..40e4085 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -87,7 +87,6 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
-import android.view.OnBackInvokedDispatcher;
import android.view.PendingInsetsController;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -109,7 +108,6 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
-import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.R;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
@@ -285,6 +283,7 @@
private Insets mBackgroundInsets = Insets.NONE;
private Insets mLastBackgroundInsets = Insets.NONE;
private boolean mDrawLegacyNavigationBarBackground;
+ private boolean mDrawLegacyNavigationBarBackgroundHandled;
private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
@@ -296,7 +295,6 @@
return true;
};
private Consumer<Boolean> mCrossWindowBlurEnabledListener;
- private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
@@ -325,7 +323,6 @@
initResizingPaints();
mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
- mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher();
}
void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -1034,6 +1031,9 @@
@Override
public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
updateColorViews(null /* insets */, true /* animate */);
+ if (mWindow != null) {
+ mWindow.dispatchOnSystemBarAppearanceChanged(appearance);
+ }
}
@Override
@@ -1168,6 +1168,9 @@
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
+ mDrawLegacyNavigationBarBackgroundHandled =
+ mWindow.onDrawLegacyNavigationBarBackgroundChanged(
+ mDrawLegacyNavigationBarBackground);
if (viewRoot != null) {
viewRoot.requestInvalidateRootRenderNode();
}
@@ -1260,7 +1263,7 @@
}
}
- if (forceConsumingNavBar) {
+ if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) {
mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
} else {
mBackgroundInsets = Insets.NONE;
@@ -1873,7 +1876,6 @@
}
mPendingInsetsController.detach();
- mOnBackInvokedDispatcher.detachFromWindow();
}
@Override
@@ -1918,11 +1920,6 @@
return mPendingInsetsController;
}
- @Override
- public WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher() {
- return mOnBackInvokedDispatcher;
- }
-
private ActionMode createActionMode(
int type, ActionMode.Callback2 callback, View originatingView) {
switch (type) {
@@ -2377,7 +2374,6 @@
}
}
}
- mOnBackInvokedDispatcher.clear();
}
@Override
@@ -2485,7 +2481,7 @@
}
private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) {
- if (!mDrawLegacyNavigationBarBackground) {
+ if (!mDrawLegacyNavigationBarBackground || mDrawLegacyNavigationBarBackgroundHandled) {
return;
}
View v = mNavigationColorViewState.view;
@@ -2659,15 +2655,6 @@
}
}
- /**
- * Returns the {@link OnBackInvokedDispatcher} on the decor view.
- */
- @Override
- @Nullable
- public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
- return mOnBackInvokedDispatcher;
- }
-
@Override
public String toString() {
return "DecorView@" + Integer.toHexString(this.hashCode()) + "["
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 7755b69..12f38a4 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -91,6 +91,8 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.OnBackInvokedDispatcher;
+import android.view.OnBackInvokedDispatcherOwner;
import android.view.ScrollCaptureCallback;
import android.view.SearchEvent;
import android.view.SurfaceHolder.Callback2;
@@ -110,6 +112,7 @@
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.window.ProxyOnBackInvokedDispatcher;
import com.android.internal.R;
import com.android.internal.view.menu.ContextMenuBuilder;
@@ -134,7 +137,8 @@
*
* @hide
*/
-public class PhoneWindow extends Window implements MenuBuilder.Callback {
+public class PhoneWindow extends Window implements MenuBuilder.Callback,
+ OnBackInvokedDispatcherOwner {
private final static String TAG = "PhoneWindow";
@@ -340,6 +344,9 @@
boolean mDecorFitsSystemWindows = true;
+ private ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher =
+ new ProxyOnBackInvokedDispatcher();
+
static class WindowManagerHolder {
static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
@@ -2146,6 +2153,7 @@
/** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
void onViewRootImplSet(ViewRootImpl viewRoot) {
viewRoot.setActivityConfigCallback(mActivityConfigCallback);
+ mProxyOnBackInvokedDispatcher.setActualDispatcherOwner(viewRoot);
applyDecorFitsSystemWindows();
}
@@ -3993,4 +4001,10 @@
public AttachedSurfaceControl getRootSurfaceControl() {
return getViewRootImplOrNull();
}
+
+ @NonNull
+ @Override
+ public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+ return mProxyOnBackInvokedDispatcher;
+ }
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index faea7706e..37c96e7 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -29,7 +29,6 @@
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
-import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -99,6 +98,10 @@
private static final String DEFAULT_PACKAGE = "android";
+ // TODO (b/215515255): remove once we full migrate to shell transitions
+ private static final boolean SHELL_TRANSITIONS_ENABLED =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
private final Context mContext;
private final String mTag;
@@ -253,6 +256,9 @@
resId = ent.array.getResourceId(animAttr, 0);
}
}
+ if (!SHELL_TRANSITIONS_ENABLED) {
+ resId = updateToLegacyIfNeeded(resId);
+ }
resId = updateToTranslucentAnimIfNeeded(resId, transit);
if (ResourceId.isValid(resId)) {
return loadAnimationSafely(context, resId, mTag);
@@ -260,6 +266,24 @@
return null;
}
+ /**
+ * Replace animations that are not compatible with the legacy transition system with ones that
+ * are compatible with it.
+ * TODO (b/215515255): remove once we full migrate to shell transitions
+ */
+ private int updateToLegacyIfNeeded(int anim) {
+ if (anim == R.anim.activity_open_enter) {
+ return R.anim.activity_open_enter_legacy;
+ } else if (anim == R.anim.activity_open_exit) {
+ return R.anim.activity_open_exit_legacy;
+ } else if (anim == R.anim.activity_close_enter) {
+ return R.anim.activity_close_enter_legacy;
+ } else if (anim == R.anim.activity_close_exit) {
+ return R.anim.activity_close_exit_legacy;
+ }
+ return anim;
+ }
+
/** Load animation by attribute Id from a specific AnimationStyle resource. */
@Nullable
public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
@@ -915,7 +939,7 @@
* animation.
*/
public HardwareBuffer createCrossProfileAppsThumbnail(
- @DrawableRes int thumbnailDrawableRes, Rect frame) {
+ Drawable thumbnailDrawable, Rect frame) {
final int width = frame.width();
final int height = frame.height();
@@ -924,14 +948,13 @@
canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
- final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
- drawable.setBounds(
+ thumbnailDrawable.setBounds(
(width - thumbnailSize) / 2,
(height - thumbnailSize) / 2,
(width + thumbnailSize) / 2,
(height + thumbnailSize) / 2);
- drawable.setTint(mContext.getColor(android.R.color.white));
- drawable.draw(canvas);
+ thumbnailDrawable.setTint(mContext.getColor(android.R.color.white));
+ thumbnailDrawable.draw(canvas);
picture.endRecording();
return Bitmap.createBitmap(picture).getHardwareBuffer();
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index a52ae10..7262e84 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -438,10 +438,16 @@
mState = state;
mStateChangeTimestampMs = timestampMs;
if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
- return;
+ mAccumulatedMultiStateChargeMicroCoulomb =
+ new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
}
for (int i = 0; i < mAccumulatedMultiStateChargeMicroCoulomb.length; i++) {
LongMultiStateCounter counter = mAccumulatedMultiStateChargeMicroCoulomb[i];
+ if (counter == null && mConfig.isSupportedMultiStateBucket(i)) {
+ counter = new LongMultiStateCounter(mConfig.mStateNames.length);
+ counter.updateValue(0, timestampMs);
+ mAccumulatedMultiStateChargeMicroCoulomb[i] = counter;
+ }
if (counter != null) {
counter.setState(state, timestampMs);
}
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index 8770267..76f7b21 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -18,28 +18,15 @@
import android.annotation.NonNull;
import android.os.Build;
-import android.os.SharedMemory;
import android.os.SystemProperties;
-import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.util.Pair;
import android.util.Slog;
-import android.util.apk.ApkSignatureVerifier;
-import android.util.apk.ByteBufferFactory;
-import android.util.apk.SignatureNotFoundException;
-
-import libcore.util.HexEncoding;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
-import java.security.DigestException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
/** Provides fsverity related operations. */
public abstract class VerityUtils {
@@ -57,8 +44,6 @@
/** SHA256 hash size. */
private static final int HASH_SIZE_BYTES = 32;
- private static final boolean DEBUG = false;
-
public static boolean isFsVeritySupported() {
return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
|| SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
@@ -123,204 +108,4 @@
private static native int measureFsverityNative(@NonNull String filePath,
@NonNull byte[] digest);
private static native int statxForFsverityNative(@NonNull String filePath);
-
- /**
- * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
- *
- * @deprecated This is only used for previous fs-verity implementation, and should never be used
- * on new devices.
- * @return {@code SetupResult} that contains the result code, and when success, the
- * {@code FileDescriptor} to read all the data from.
- */
- @Deprecated
- public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
- if (DEBUG) {
- Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
- }
- SharedMemory shm = null;
- try {
- final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
- if (signedVerityHash == null) {
- if (DEBUG) {
- Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
- }
- return SetupResult.skipped();
- }
-
- Pair<SharedMemory, Integer> result =
- generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
- shm = result.first;
- int contentSize = result.second;
- FileDescriptor rfd = shm.getFileDescriptor();
- if (rfd == null || !rfd.valid()) {
- return SetupResult.failed();
- }
- return SetupResult.ok(Os.dup(rfd), contentSize);
- } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException
- | SignatureNotFoundException | ErrnoException e) {
- Slog.e(TAG, "Failed to set up apk verity: ", e);
- return SetupResult.failed();
- } finally {
- if (shm != null) {
- shm.close();
- }
- }
- }
-
- /**
- * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
- * @deprecated This is only used for previous fs-verity implementation, and should never be used
- * on new devices.
- */
- @Deprecated
- public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
- throws NoSuchAlgorithmException, DigestException, IOException {
- return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
- }
-
- /**
- * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
- * @deprecated This is only used for previous fs-verity implementation, and should never be used
- * on new devices.
- */
- @Deprecated
- public static byte[] getVerityRootHash(@NonNull String apkPath)
- throws IOException, SignatureNotFoundException {
- return ApkSignatureVerifier.getVerityRootHash(apkPath);
- }
-
- /**
- * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
- * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
- * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
- * length equals to the returned {@code Integer}.
- */
- private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
- @NonNull byte[] expectedRootHash)
- throws IOException, DigestException, NoSuchAlgorithmException,
- SignatureNotFoundException {
- TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
- byte[] generatedRootHash =
- ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
- // We only generate Merkle tree once here, so it's important to make sure the root hash
- // matches the signed one in the apk.
- if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
- throw new SecurityException("verity hash mismatch: "
- + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
- }
-
- int contentSize = shmBufferFactory.getBufferLimit();
- SharedMemory shm = shmBufferFactory.releaseSharedMemory();
- if (shm == null) {
- throw new IllegalStateException("Failed to generate verity tree into shared memory");
- }
- if (!shm.setProtect(OsConstants.PROT_READ)) {
- throw new SecurityException("Failed to set up shared memory correctly");
- }
- return Pair.create(shm, contentSize);
- }
-
- private static String bytesToString(byte[] bytes) {
- return HexEncoding.encodeToString(bytes);
- }
-
- /**
- * @deprecated This is only used for previous fs-verity implementation, and should never be used
- * on new devices.
- */
- @Deprecated
- public static class SetupResult {
- /** Result code if verity is set up correctly. */
- private static final int RESULT_OK = 1;
-
- /** Result code if signature is not provided. */
- private static final int RESULT_SKIPPED = 2;
-
- /** Result code if the setup failed. */
- private static final int RESULT_FAILED = 3;
-
- private final int mCode;
- private final FileDescriptor mFileDescriptor;
- private final int mContentSize;
-
- /** @deprecated */
- @Deprecated
- public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
- return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
- }
-
- /** @deprecated */
- @Deprecated
- public static SetupResult skipped() {
- return new SetupResult(RESULT_SKIPPED, null, -1);
- }
-
- /** @deprecated */
- @Deprecated
- public static SetupResult failed() {
- return new SetupResult(RESULT_FAILED, null, -1);
- }
-
- private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
- this.mCode = code;
- this.mFileDescriptor = fileDescriptor;
- this.mContentSize = contentSize;
- }
-
- public boolean isFailed() {
- return mCode == RESULT_FAILED;
- }
-
- public boolean isOk() {
- return mCode == RESULT_OK;
- }
-
- public @NonNull FileDescriptor getUnownedFileDescriptor() {
- return mFileDescriptor;
- }
-
- public int getContentSize() {
- return mContentSize;
- }
- }
-
- /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
- private static class TrackedShmBufferFactory implements ByteBufferFactory {
- private SharedMemory mShm;
- private ByteBuffer mBuffer;
-
- @Override
- public ByteBuffer create(int capacity) {
- try {
- if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
- // NB: This method is supposed to be called once according to the contract with
- // ApkSignatureSchemeV2Verifier.
- if (mBuffer != null) {
- throw new IllegalStateException("Multiple instantiation from this factory");
- }
- mShm = SharedMemory.create("apkverity", capacity);
- if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) {
- throw new SecurityException("Failed to set protection");
- }
- mBuffer = mShm.mapReadWrite();
- return mBuffer;
- } catch (ErrnoException e) {
- throw new SecurityException("Failed to set protection", e);
- }
- }
-
- public SharedMemory releaseSharedMemory() {
- if (mBuffer != null) {
- SharedMemory.unmap(mBuffer);
- mBuffer = null;
- }
- SharedMemory tmp = mShm;
- mShm = null;
- return tmp;
- }
-
- public int getBufferLimit() {
- return mBuffer == null ? -1 : mBuffer.limit();
- }
- }
}
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/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a5cf7ce..51eb429 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -20,15 +20,18 @@
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.service.notification.StatusBarNotification;
import android.view.InsetsVisibilities;
import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
@@ -166,6 +169,8 @@
* Used to hide the authentication dialog, e.g. when the application cancels authentication.
*/
void hideAuthenticationDialog();
+ /* Used to notify the biometric service of events that occur outside of an operation. */
+ void setBiometicContextListener(in IBiometricContextListener listener);
/**
* Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -293,4 +298,15 @@
void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback);
void cancelRequestAddTile(in String packageName);
+
+ /** Notifies System UI about an update to the media tap-to-transfer sender state. */
+ void updateMediaTapToTransferSenderDisplay(
+ int displayState,
+ in MediaRoute2Info routeInfo,
+ in IUndoMediaTransferCallback undoCallback);
+
+ /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
+ void updateMediaTapToTransferReceiverDisplay(
+ int displayState,
+ in MediaRoute2Info routeInfo);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3c6b7ff..0c45e5b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -20,16 +20,21 @@
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.graphics.Rect;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
import android.net.Uri;
import android.os.Bundle;
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.IUndoMediaTransferCallback;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
@@ -123,6 +128,8 @@
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
void hideAuthenticationDialog();
+ // Used to notify the biometric service of events that occur outside of an operation.
+ void setBiometicContextListener(in IBiometricContextListener listener);
/**
* Sets an instance of IUdfpsHbmListener for UdfpsController.
@@ -178,4 +185,28 @@
* @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);
+
+ /** Notifies System UI about an update to the media tap-to-transfer sender state. */
+ void updateMediaTapToTransferSenderDisplay(
+ int displayState,
+ in MediaRoute2Info routeInfo,
+ in IUndoMediaTransferCallback undoCallback);
+
+ /** Notifies System UI about an update to the media tap-to-transfer receiver state. */
+ void updateMediaTapToTransferReceiverDisplay(
+ int displayState,
+ in MediaRoute2Info routeInfo);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
similarity index 68%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
rename to core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
index b47be87..3dd2980 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
+++ b/core/java/com/android/internal/statusbar/IUndoMediaTransferCallback.aidl
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package com.android.internal.statusbar;
/**
- * An interface that will be invoked by System UI if the user choose to undo a transfer.
- *
- * Other services will implement this interface and System UI will invoke it.
+ * An interface that will be invoked if the user chooses to undo a transfer.
*/
-interface IUndoTransferCallback {
+interface IUndoMediaTransferCallback {
/**
- * Invoked by SystemUI when the user requests to undo the media transfer that just occurred.
+ * Invoked to notify callers that the user has chosen to undo the media transfer that just
+ * occurred.
*
* Implementors of this method are repsonsible for actually undoing the transfer.
*/
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe4323..17b84ff 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.ColorInt;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -72,9 +73,30 @@
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
+ return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+ }
+
+ /**
+ * Returns a default user icon in a particular color.
+ *
+ * @param resources resources object to fetch the user icon
+ * @param color the color used for the icon
+ */
+ public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+ icon.setColorFilter(color, Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
+
+ /**
+ * Returns an array containing colors to be used for default user icons.
+ */
+ public static int[] getUserIconColors(Resources resources) {
+ int[] result = new int[USER_ICON_COLORS.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+ }
+ return result;
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 6a626ee..d2bc344 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -37,7 +37,8 @@
*/
oneway interface IInputMethod {
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported);
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
in IInlineSuggestionsRequestCallback cb);
@@ -47,7 +48,10 @@
void unbindInput();
void startInput(in IBinder startInputToken, in IInputContext inputContext,
- in EditorInfo attribute, boolean restarting);
+ in EditorInfo attribute, boolean restarting,
+ boolean shouldShowImeSwitcherWhenImeIsShown);
+
+ void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown);
void createSession(in InputChannel channel, IInputSessionCallback callback);
@@ -61,5 +65,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/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
index 4b89bf5..3ab9a33 100644
--- a/core/java/com/android/internal/view/RootViewSurfaceTaker.java
+++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
@@ -15,12 +15,10 @@
*/
package com.android.internal.view;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.view.InputQueue;
import android.view.PendingInsetsController;
import android.view.SurfaceHolder;
-import android.window.WindowOnBackInvokedDispatcher;
/** hahahah */
public interface RootViewSurfaceTaker {
@@ -31,6 +29,4 @@
InputQueue.Callback willYouTakeTheInputQueue();
void onRootViewScrollYChanged(int scrollY);
@Nullable PendingInsetsController providePendingInsetsController();
- /** @hide */
- @NonNull WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher();
}
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/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 39f17e5..93864fa 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -237,6 +237,10 @@
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
+ // These are the packages that are exempted from the background restriction applied
+ // by the system automatically, i.e., due to high background current drain.
+ final ArraySet<String> mBgRestrictionExemption = new ArraySet<>();
+
// These are the package names of apps which should be automatically granted domain verification
// for all of their domains. The only way these apps can be overridden by the user is by
// explicitly disabling overall link handling support in app info.
@@ -389,6 +393,10 @@
return mAllowIgnoreLocationSettings;
}
+ public ArraySet<String> getBgRestrictionExemption() {
+ return mBgRestrictionExemption;
+ }
+
public ArraySet<String> getLinkedApps() {
return mLinkedApps;
}
@@ -1049,6 +1057,20 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "bg-restriction-exemption": {
+ if (allowOverrideAppRestrictions) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mBgRestrictionExemption.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "default-enabled-vr-app": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 430d84e..8bb9a0a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -195,7 +195,6 @@
"android_util_FileObserver.cpp",
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
- "android_server_NetworkManagementSocketTagger.cpp",
"android_ddm_DdmHandleNativeHeap.cpp",
"android_backup_BackupDataInput.cpp",
"android_backup_BackupDataOutput.cpp",
@@ -310,6 +309,8 @@
"libdl_android",
"libtimeinstate",
"server_configurable_flags",
+ // TODO: delete when ConnectivityT moves to APEX.
+ "libframework-connectivity-tiramisu-jni",
],
export_shared_lib_headers: [
// our headers include libnativewindow's public headers
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f4296be..cde71cf 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -164,7 +164,6 @@
extern int register_android_text_Hyphenator(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
-extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
@@ -1623,7 +1622,6 @@
REG_JNI(register_android_media_midi),
REG_JNI(register_android_opengl_classes),
- REG_JNI(register_android_server_NetworkManagementSocketTagger),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 9a460f5..24c0d2a 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -66,6 +66,7 @@
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
### Text ###
per-file android_text_* = file:/core/java/android/text/OWNERS
diff --git a/core/jni/android/opengl/OWNERS b/core/jni/android/opengl/OWNERS
new file mode 100644
index 0000000..ce4b907
--- /dev/null
+++ b/core/jni/android/opengl/OWNERS
@@ -0,0 +1 @@
+file:/graphics/java/android/graphics/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 82601ba..d852265 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -643,6 +643,8 @@
return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB);
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F);
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return (type == GL_UNSIGNED_INT_2_10_10_10_REV && internalformat == GL_RGB10_A2);
default:
break;
}
@@ -676,6 +678,8 @@
return GL_RGB;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_RGBA16F;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return GL_RGB10_A2;
default:
return -1;
}
@@ -693,6 +697,8 @@
return GL_UNSIGNED_SHORT_5_6_5;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_HALF_FLOAT;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return GL_UNSIGNED_INT_2_10_10_10_REV;
default:
return -1;
}
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index b25b4f7..f462523 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -85,7 +85,7 @@
sp<GraphicBuffer> buffer = new GraphicBuffer(width, height, pixelFormat, layers,
grallocUsage, std::string("HardwareBuffer pid [") + std::to_string(getpid()) +"]");
status_t error = buffer->initCheck();
- if (error < 0) {
+ if (error != OK) {
if (kDebugGraphicBuffer) {
ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()");
}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index d039bcf..78b403c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -133,6 +133,18 @@
MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
}
+uint64_t htonll(uint64_t ll) {
+ constexpr uint32_t kBytesToTest = 0x12345678;
+ constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest;
+ constexpr bool kIsLittleEndian = kFirstByte == 0x78;
+
+ if constexpr (kIsLittleEndian) {
+ return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32);
+ } else {
+ return ll;
+ }
+}
+
static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
if (string == "") {
return gStringOffsets.emptyString;
@@ -193,7 +205,8 @@
int32_t id = nativeSensor.getId();
env->CallVoidMethod(sensor, sensorOffsets.setId, id);
Sensor::uuid_t uuid = nativeSensor.getUuid();
- env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]);
+ env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]),
+ htonll(uuid.i64[1]));
}
return sensor;
}
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..2bec733 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -18,25 +18,25 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioSystem-JNI"
-#include <utils/Log.h>
-
-#include <sstream>
-#include <vector>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
#include <android/media/AudioVibratorInfo.h>
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
+#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <jni.h>
#include <media/AudioContainers.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
#include <media/MicrophoneInfo.h>
+#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <system/audio.h>
#include <system/audio_policy.h>
+#include <utils/Log.h>
+
+#include <sstream>
+#include <vector>
+
#include "android_media_AudioAttributes.h"
#include "android_media_AudioDescriptor.h"
#include "android_media_AudioDeviceAttributes.h"
@@ -46,6 +46,7 @@
#include "android_media_AudioProfile.h"
#include "android_media_MicrophoneInfo.h"
#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
// ----------------------------------------------------------------------------
@@ -204,6 +205,12 @@
jclass gAudioProfileClass;
jmethodID gAudioProfileCstor;
+static struct {
+ jfieldID mSamplingRates;
+ jfieldID mChannelMasks;
+ jfieldID mChannelIndexMasks;
+ jfieldID mEncapsulationType;
+} gAudioProfileFields;
jclass gVibratorClass;
static struct {
@@ -578,18 +585,26 @@
env->DeleteLocalRef(clazz);
}
-static jint
-android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jint state, jstring device_address, jstring device_name,
- jint codec)
-{
- const char *c_address = env->GetStringUTFChars(device_address, NULL);
- const char *c_name = env->GetStringUTFChars(device_name, NULL);
- int status = check_AudioSystem_Command(AudioSystem::setDeviceConnectionState(static_cast <audio_devices_t>(device),
- static_cast <audio_policy_dev_state_t>(state),
- c_address, c_name,
- static_cast <audio_format_t>(codec)));
- env->ReleaseStringUTFChars(device_address, c_address);
- env->ReleaseStringUTFChars(device_name, c_name);
+static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz,
+ jint state, jobject jParcel,
+ jint codec) {
+ int status;
+ if (Parcel *parcel = parcelForJavaObject(env, jParcel); parcel != nullptr) {
+ android::media::audio::common::AudioPort port{};
+ if (status_t statusOfParcel = port.readFromParcel(parcel); statusOfParcel == OK) {
+ status = check_AudioSystem_Command(
+ AudioSystem::setDeviceConnectionState(static_cast<audio_policy_dev_state_t>(
+ state),
+ port,
+ static_cast<audio_format_t>(codec)));
+ } else {
+ ALOGE("Failed to read from parcel: %s", statusToString(statusOfParcel).c_str());
+ status = kAudioStatusError;
+ }
+ } else {
+ ALOGE("Failed to retrieve the native parcel from Java parcel");
+ status = kAudioStatusError;
+ }
return (jint) status;
}
@@ -1236,6 +1251,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;
@@ -1281,81 +1355,50 @@
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}
+
for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) {
- size_t numPositionMasks = 0;
- size_t numIndexMasks = 0;
- // count up how many masks are positional and indexed
- for (size_t index = 0; index < nAudioPort->audio_profiles[i].num_channel_masks; index++) {
- const audio_channel_mask_t mask = nAudioPort->audio_profiles[i].channel_masks[index];
- if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
- numIndexMasks++;
- } else {
- numPositionMasks++;
- }
- }
-
- ScopedLocalRef<jintArray> jSamplingRates(env,
- env->NewIntArray(nAudioPort->audio_profiles[i]
- .num_sample_rates));
- ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks));
- ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks));
- if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) {
+ jobject jAudioProfile = nullptr;
+ jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i],
+ useInMask);
+ if (jStatus != NO_ERROR) {
jStatus = (jint)AUDIO_JAVA_ERROR;
goto exit;
}
+ env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile);
- if (nAudioPort->audio_profiles[i].num_sample_rates) {
- env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/,
- nAudioPort->audio_profiles[i].num_sample_rates,
- (jint *)nAudioPort->audio_profiles[i].sample_rates);
- }
-
- // put the masks in the output arrays
- for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0;
- maskIndex < nAudioPort->audio_profiles[i].num_channel_masks; maskIndex++) {
- const audio_channel_mask_t mask =
- nAudioPort->audio_profiles[i].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(nAudioPort->audio_profiles[i].encapsulation_type,
- &encapsulationType) != NO_ERROR) {
- ALOGW("Unknown encapsualtion type for JAVA API: %u",
- nAudioPort->audio_profiles[i].encapsulation_type);
- continue;
- }
-
- ScopedLocalRef<jobject>
- jAudioProfile(env,
- env->NewObject(gAudioProfileClass, gAudioProfileCstor,
- audioFormatFromNative(
- nAudioPort->audio_profiles[i].format),
- jSamplingRates.get(), jChannelMasks.get(),
- jChannelIndexMasks.get(), encapsulationType));
- if (jAudioProfile == nullptr) {
- jStatus = (jint)AUDIO_JAVA_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get());
if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) {
hasFloat = true;
} else if (jPcmFloatProfileFromExtendedInteger.get() == nullptr &&
audio_is_linear_pcm(nAudioPort->audio_profiles[i].format) &&
audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) {
+ ScopedLocalRef<jintArray>
+ jSamplingRates(env,
+ (jintArray)
+ env->GetObjectField(jAudioProfile,
+ gAudioProfileFields.mSamplingRates));
+ ScopedLocalRef<jintArray>
+ jChannelMasks(env,
+ (jintArray)
+ env->GetObjectField(jAudioProfile,
+ gAudioProfileFields.mChannelMasks));
+ ScopedLocalRef<jintArray>
+ jChannelIndexMasks(env,
+ (jintArray)env->GetObjectField(jAudioProfile,
+ gAudioProfileFields
+ .mChannelIndexMasks));
+ int encapsulationType =
+ env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType);
+
jPcmFloatProfileFromExtendedInteger.reset(
env->NewObject(gAudioProfileClass, gAudioProfileCstor,
audioFormatFromNative(AUDIO_FORMAT_PCM_FLOAT),
jSamplingRates.get(), jChannelMasks.get(),
jChannelIndexMasks.get(), encapsulationType));
}
+
+ if (jAudioProfile != nullptr) {
+ env->DeleteLocalRef(jAudioProfile);
+ }
}
if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) {
// R and earlier compatibility - add ENCODING_PCM_FLOAT to the end
@@ -2479,6 +2522,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 +2861,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[] =
@@ -2830,7 +2921,7 @@
{"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId},
{"newAudioPlayerId", "()I", (void *)android_media_AudioSystem_newAudioPlayerId},
{"newAudioRecorderId", "()I", (void *)android_media_AudioSystem_newAudioRecorderId},
- {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I",
+ {"setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
(void *)android_media_AudioSystem_setDeviceConnectionState},
{"getDeviceConnectionState", "(ILjava/lang/String;)I",
(void *)android_media_AudioSystem_getDeviceConnectionState},
@@ -2915,6 +3006,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 +3052,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",
@@ -3174,6 +3269,14 @@
jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile");
gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass);
gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "<init>", "(I[I[I[II)V");
+ gAudioProfileFields.mSamplingRates =
+ GetFieldIDOrDie(env, audioProfileClass, "mSamplingRates", "[I");
+ gAudioProfileFields.mChannelMasks =
+ GetFieldIDOrDie(env, audioProfileClass, "mChannelMasks", "[I");
+ gAudioProfileFields.mChannelIndexMasks =
+ GetFieldIDOrDie(env, audioProfileClass, "mChannelIndexMasks", "[I");
+ gAudioProfileFields.mEncapsulationType =
+ GetFieldIDOrDie(env, audioProfileClass, "mEncapsulationType", "I");
jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor");
gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass);
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
deleted file mode 100644
index 1be1873..0000000
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2011, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "NMST_QTagUidNative"
-
-#include <android/multinetwork.h>
-#include <cutils/qtaguid.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include "jni.h"
-
-namespace android {
-
-static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
- jint tagNum, jint uid) {
- int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
- if (env->ExceptionCheck()) {
- ALOGE("Can't get FileDescriptor num");
- return (jint)-1;
- }
-
- int res = android_tag_socket_with_uid(userFd, tagNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
- int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-
- if (env->ExceptionCheck()) {
- ALOGE("Can't get FileDescriptor num");
- return (jint)-1;
- }
-
- int res = android_untag_socket(userFd);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) {
- int res = qtaguid_setCounterSet(setNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) {
- int res = qtaguid_deleteTagData(tagNum, uid);
- if (res < 0) {
- return (jint)-errno;
- }
- return (jint)res;
-}
-
-static const JNINativeMethod gQTagUidMethods[] = {
- { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
- { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
- { "native_setCounterSet", "(II)I", (void*)setCounterSet},
- { "native_deleteTagData", "(II)I", (void*)deleteTagData},
-};
-
-int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods));
-}
-
-};
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 61b91dd..13ca133 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -873,7 +873,7 @@
const char* exceptionToThrow;
char msg[128];
// TransactionTooLargeException is a checked exception, only throw from certain methods.
- // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION
+ // TODO(b/28321379): Transaction size is the most common cause for FAILED_TRANSACTION
// but it is not the only one. The Binder driver can return BR_FAILED_REPLY
// for other reasons also, such as if the transaction is malformed or
// refers to an FD that has been closed. We should change the driver
@@ -890,8 +890,9 @@
exceptionToThrow = (canThrowRemoteException)
? "android/os/DeadObjectException"
: "java/lang/RuntimeException";
- snprintf(msg, sizeof(msg)-1,
- "Transaction failed on small parcel; remote process probably died");
+ snprintf(msg, sizeof(msg) - 1,
+ "Transaction failed on small parcel; remote process probably died, but "
+ "this could also be caused by running out of binder buffer space");
}
jniThrowException(env, exceptionToThrow, msg);
} break;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7c67cbc..a6fbf09 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -235,9 +235,7 @@
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
- DIR *d;
char proc_path[255];
- struct dirent *de;
if (!verifyGroup(env, grp)) {
return;
@@ -277,84 +275,8 @@
}
}
- sprintf(proc_path, "/proc/%d/task", pid);
- if (!(d = opendir(proc_path))) {
- // If the process exited on us, don't generate an exception
- if (errno != ENOENT)
- signalExceptionForGroupError(env, errno, pid);
- return;
- }
-
- while ((de = readdir(d))) {
- int t_pid;
- int t_pri;
- std::string taskprofile;
-
- if (de->d_name[0] == '.')
- continue;
- t_pid = atoi(de->d_name);
-
- if (!t_pid) {
- ALOGE("Error getting pid for '%s'\n", de->d_name);
- continue;
- }
-
- t_pri = getpriority(PRIO_PROCESS, t_pid);
-
- if (t_pri <= ANDROID_PRIORITY_AUDIO) {
- int scheduler = sched_getscheduler(t_pid) & ~SCHED_RESET_ON_FORK;
- if ((scheduler == SCHED_FIFO) || (scheduler == SCHED_RR)) {
- // This task wants to stay in its current audio group so it can keep its budget
- // don't update its cpuset or cgroup
- continue;
- }
- }
-
- errno = 0;
- // grp == SP_BACKGROUND. Set background cpuset policy profile for all threads.
- if (grp == SP_BACKGROUND) {
- if (!SetTaskProfiles(t_pid, {"CPUSET_SP_BACKGROUND"}, true)) {
- signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
- break;
- }
- continue;
- }
-
- // grp != SP_BACKGROUND. Only change the cpuset cgroup for low priority thread, so it could
- // preserve it sched policy profile setting.
- if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
- switch (grp) {
- case SP_SYSTEM:
- taskprofile = "ServiceCapacityLow";
- break;
- case SP_RESTRICTED:
- taskprofile = "ServiceCapacityRestricted";
- break;
- case SP_FOREGROUND:
- case SP_AUDIO_APP:
- case SP_AUDIO_SYS:
- taskprofile = "ProcessCapacityHigh";
- break;
- case SP_TOP_APP:
- taskprofile = "ProcessCapacityMax";
- break;
- default:
- taskprofile = "ProcessCapacityNormal";
- break;
- }
- if (!SetTaskProfiles(t_pid, {taskprofile}, true)) {
- signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
- break;
- }
- // Change the cpuset policy profile for non-low priority thread according to the grp
- } else {
- if (!SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true)) {
- signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
- break;
- }
- }
- }
- closedir(d);
+ if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+ signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
}
void android_os_Process_setProcessFrozen(
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a8cf253..9915913 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -104,6 +104,7 @@
jfieldID hdrCapabilities;
jfieldID autoLowLatencyModeSupported;
jfieldID gameContentTypeSupported;
+ jfieldID preferredBootDisplayMode;
} gDynamicDisplayInfoClassInfo;
static struct {
@@ -1301,6 +1302,9 @@
env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.gameContentTypeSupported,
info.gameContentTypeSupported);
+
+ env->SetIntField(object, gDynamicDisplayInfoClassInfo.preferredBootDisplayMode,
+ info.preferredBootDisplayMode);
return object;
}
@@ -1638,6 +1642,27 @@
}
}
+static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) {
+ bool isBootDisplayModeSupported = false;
+ SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported);
+ return static_cast<jboolean>(isBootDisplayModeSupported);
+}
+
+static void nativeSetBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject,
+ jint displayModId) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+ if (token == NULL) return;
+
+ SurfaceComposerClient::setBootDisplayMode(token, displayModId);
+}
+
+static void nativeClearBootDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObject) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+ if (token == NULL) return;
+
+ SurfaceComposerClient::clearBootDisplayMode(token);
+}
+
static void nativeSetAutoLowLatencyMode(JNIEnv* env, jclass clazz, jobject tokenObject, jboolean on) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
if (token == NULL) return;
@@ -2046,6 +2071,12 @@
(void*)nativeGetDisplayNativePrimaries },
{"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z",
(void*)nativeSetActiveColorMode},
+ {"nativeGetBootDisplayModeSupport", "()Z",
+ (void*)nativeGetBootDisplayModeSupport },
+ {"nativeSetBootDisplayMode", "(Landroid/os/IBinder;I)V",
+ (void*)nativeSetBootDisplayMode },
+ {"nativeClearBootDisplayMode", "(Landroid/os/IBinder;)V",
+ (void*)nativeClearBootDisplayMode },
{"nativeSetAutoLowLatencyMode", "(Landroid/os/IBinder;Z)V",
(void*)nativeSetAutoLowLatencyMode },
{"nativeSetGameContentType", "(Landroid/os/IBinder;Z)V",
@@ -2184,6 +2215,8 @@
GetFieldIDOrDie(env, dynamicInfoClazz, "autoLowLatencyModeSupported", "Z");
gDynamicDisplayInfoClassInfo.gameContentTypeSupported =
GetFieldIDOrDie(env, dynamicInfoClazz, "gameContentTypeSupported", "Z");
+ gDynamicDisplayInfoClassInfo.preferredBootDisplayMode =
+ GetFieldIDOrDie(env, dynamicInfoClazz, "preferredBootDisplayMode", "I");
jclass modeClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayMode");
gDisplayModeClassInfo.clazz = MakeGlobalRefOrDie(env, modeClazz);
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/proto/android/server/Android.bp b/core/proto/android/server/Android.bp
new file mode 100644
index 0000000..362daa7
--- /dev/null
+++ b/core/proto/android/server/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "srcs_bluetooth_manager_service_proto",
+ srcs: [
+ "bluetooth_manager_service.proto",
+ ],
+ visibility: ["//packages/modules/Bluetooth:__subpackages__"],
+}
+
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index acb7429..04f4d7b 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -127,7 +127,7 @@
optional bool is_light_device_idle_mode = 25;
// True if we are currently in device idle mode.
optional bool is_device_idle_mode = 26;
- // Set of app ids that we will always respect the wake locks for.
+ // Set of app ids that we will respect the wake locks for while in device idle mode.
repeated int32 device_idle_whitelist = 27;
// Set of app ids that are temporarily allowed to acquire wakelocks due to
// high-pri message
@@ -187,6 +187,8 @@
// Whether or not the current enhanced discharge prediction is personalized based on device
// usage or not.
optional bool is_enhanced_discharge_prediction_personalized = 54;
+ optional bool is_low_power_standby_active = 55;
+ optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
@@ -209,6 +211,8 @@
// When this wake lock is released, poke the user activity timer
// so the screen stays on for a little longer.
optional bool is_on_after_release = 2;
+ // The wakelock is held by the system server on request by another app.
+ optional bool system_wakelock = 3;
}
optional .android.os.WakeLockLevelEnum lock_level = 1;
@@ -428,3 +432,39 @@
// Next tag: 23
}
+
+message LowPowerStandbyControllerDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // True if Low Power Standby is active
+ optional bool is_active = 1;
+
+ // True if Low Power Standby is enabled
+ optional bool is_enabled = 2;
+
+ // True if Low Power Standby is supported
+ optional bool is_supported_config = 3;
+
+ // True if Low Power Standby is enabled by default
+ optional bool is_enabled_by_default_config = 4;
+
+ // True if the device is currently interactive
+ optional bool is_interactive = 5;
+
+ // Time (in elapsedRealtime) when the device was last interactive
+ optional int64 last_interactive_time = 6;
+
+ // Timeout (in milliseconds) after becoming non-interactive that Low Power Standby can activate
+ optional int32 standby_timeout_config = 7;
+
+ // True if the device has entered idle mode since becoming non-interactive
+ optional bool idle_since_non_interactive = 8;
+
+ // True if the device is currently in idle mode
+ optional bool is_device_idle = 9;
+
+ // Set of app ids that are exempt form low power standby
+ repeated int32 allowlist = 10;
+
+ // Next tag: 11
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c62d964..74bf152 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -107,6 +107,7 @@
<protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+ <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" />
<protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
<!-- @deprecated This is rarely used and will be phased out soon. -->
@@ -397,6 +398,7 @@
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
<protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
<protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
@@ -715,8 +717,10 @@
<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" />
+ <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -1142,6 +1146,15 @@
android:description="@string/permdesc_accessBackgroundLocation"
android:protectionLevel="dangerous|instant" />
+ <!-- Allows an application (emergency or advanced driver-assistance app) to bypass
+ location settings.
+ <p>Not for use by third-party applications.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.LOCATION_BYPASS"
+ android:protectionLevel="signature|privileged"/>
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the call log -->
<!-- ====================================================================== -->
@@ -1645,7 +1658,7 @@
android:protectionLevel="normal"
android:permissionFlags="removed"/>
- <!-- @hide We need to keep this around for backwards compatibility -->
+ <!-- @SystemApi @hide We need to keep this around for backwards compatibility -->
<permission android:name="android.permission.WRITE_SMS"
android:protectionLevel="normal"
android:permissionFlags="removed"/>
@@ -1723,7 +1736,7 @@
<permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
+ <!-- @SystemApi Allows an application to monitor incoming Bluetooth MAP messages, to record
or perform processing on them. -->
<!-- @hide -->
<permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
@@ -1837,11 +1850,12 @@
<permission android:name="android.permission.ACCESS_MOCK_LOCATION"
android:protectionLevel="signature" />
- <!-- @SystemApi @hide Allows automotive applications to control location
+ <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ Allows automotive applications to control location
suspend state for power management use cases.
<p>Not for use by third-party applications.
-->
- <permission android:name="android.permission.AUTOMOTIVE_GNSS_CONTROLS"
+ <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
android:protectionLevel="signature|privileged" />
<!-- ======================================= -->
@@ -2069,6 +2083,11 @@
<permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows an application to manage ethernet networks.
+ <p>Not for use by third-party or privileged applications. -->
+ <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS"
+ android:protectionLevel="signature" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
@@ -3119,8 +3138,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 +3147,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" />
@@ -3657,6 +3679,13 @@
<permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
android:protectionLevel="signature|privileged|development" />
+ <!-- @hide @SystemApi Must be required by a
+ {@link com.android.service.tracing.TraceReportService}, to ensure that only the system
+ can bind to it.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @hide @SystemApi @TestApi
Allow an application to approve incident and bug reports to be
shared off-device. There can be only one application installed on the
@@ -4157,6 +4186,16 @@
<permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by a
+ android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE"
+ android:protectionLevel="signature" />
+
+
<!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4659,6 +4698,13 @@
<permission android:name="android.permission.READ_FRAME_BUFFER"
android:protectionLevel="signature|recents" />
+ <!-- @SystemApi Allows an application to change the touch mode state.
+ Without this permission, an app can only change the touch mode
+ if it currently has focus.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to use InputFlinger's low level features.
@hide -->
<permission android:name="android.permission.ACCESS_INPUT_FLINGER"
@@ -4951,6 +4997,11 @@
<permission android:name="android.permission.USER_ACTIVITY"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @SystemApi Allows an application to manage Low Power Standby settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY"
+ android:protectionLevel="signature|privileged" />
+
<!-- @hide Allows low-level access to tun tap driver -->
<permission android:name="android.permission.NET_TUNNELING"
android:protectionLevel="signature" />
@@ -5809,6 +5860,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"
@@ -5839,6 +5895,13 @@
<permission android:name="android.permission.MANAGE_SMARTSPACE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the wallpaper effects
+ generation service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION"
+ android:protectionLevel="signature" />
+
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
@@ -5938,6 +6001,10 @@
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
android:protectionLevel="signature" />
+ <!-- @hide Permission that suppresses the notification when the clipboard is accessed.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
+ android:protectionLevel="signature" />
<!-- @SystemApi Allows modifying accessibility state.
@hide -->
@@ -6081,13 +6148,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,11 +6198,22 @@
<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"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession
+ APIs and overlay a view on top of the game's Activity.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
when they are performing reboot-blocking work.
@hide -->
@@ -6262,6 +6340,15 @@
<permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or
+ number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}).
+ When the system arranges floating windows onscreen, it might decide to ignore keep-clear
+ areas from windows, whose owner does not have this permission.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
@@ -6514,12 +6601,27 @@
android:exported="false">
</activity>
+ <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui"
+ android:label="@string/log_access_confirmation_title"
+ android:exported="false">
+ </activity>
+
<activity android:name="com.android.server.notification.NASLearnMoreActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
android:exported="false">
</activity>
+ <activity android:name="android.service.games.GameSessionTrampolineActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_GAME_ACTIVITY"
+ android:theme="@style/Theme.Translucent.NoTitleBar">
+ </activity>
+
<receiver android:name="com.android.server.BootReceiver"
android:exported="true"
android:systemUserOnly="true">
diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml
index 9fa7c54..0fefb51 100644
--- a/core/res/res/anim/activity_close_enter.xml
+++ b/core/res/res/anim/activity_close_enter.xml
@@ -19,16 +19,37 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <scale
- android:fromXScale="1.1"
- android:toXScale="1"
- android:fromYScale="1.1"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="0"
+ android:duration="450" />
+
+ <translate
+ android:fromXDelta="-10%"
+ android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="0"
+ android:fromExtendTop="0"
+ android:fromExtendRight="10%"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="0"
+ android:toExtendTop="0"
+ android:toExtendRight="10%"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_enter_legacy.xml b/core/res/res/anim/activity_close_enter_legacy.xml
new file mode 100644
index 0000000..9fa7c54
--- /dev/null
+++ b/core/res/res/anim/activity_close_enter_legacy.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale
+ android:fromXScale="1.1"
+ android:toXScale="1"
+ android:fromYScale="1.1"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml
index 1599ae8..f807c26 100644
--- a/core/res/res/anim/activity_close_exit.xml
+++ b/core/res/res/anim/activity_close_exit.xml
@@ -18,27 +18,38 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top">
+ android:shareInterpolator="false">
+
<alpha
- android:fromAlpha="1"
+ android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/linear"
- android:startOffset="33"
- android:duration="50"/>
- <scale
- android:fromXScale="1"
- android:toXScale="0.9"
- android:fromYScale="1"
- android:toYScale="0.9"
- android:pivotX="50%"
- android:pivotY="50%"
+ android:startOffset="35"
+ android:duration="83" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="10%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="10%"
+ android:fromExtendTop="0"
+ android:fromExtendRight="0"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="10%"
+ android:toExtendTop="0"
+ android:toExtendRight="0"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
diff --git a/core/res/res/anim/activity_close_exit_legacy.xml b/core/res/res/anim/activity_close_exit_legacy.xml
new file mode 100644
index 0000000..1599ae8
--- /dev/null
+++ b/core/res/res/anim/activity_close_exit_legacy.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:zAdjustment="top">
+ <alpha
+ android:fromAlpha="1"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="33"
+ android:duration="50"/>
+ <scale
+ android:fromXScale="1"
+ android:toXScale="0.9"
+ android:fromYScale="1"
+ android:toYScale="0.9"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml
index 38d3e8e..1674dab 100644
--- a/core/res/res/anim/activity_open_enter.xml
+++ b/core/res/res/anim/activity_open_enter.xml
@@ -18,6 +18,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+
<alpha
android:fromAlpha="0"
android:toAlpha="1.0"
@@ -26,17 +27,27 @@
android:fillAfter="true"
android:interpolator="@interpolator/linear"
android:startOffset="50"
- android:duration="50"/>
- <scale
- android:fromXScale="0.85"
- android:toXScale="1"
- android:fromYScale="0.85"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
+ android:duration="83" />
+
+ <translate
+ android:fromXDelta="10%"
+ android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="10%"
+ android:fromExtendTop="0"
+ android:fromExtendRight="0"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="10%"
+ android:toExtendTop="0"
+ android:toExtendRight="0"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
diff --git a/core/res/res/anim/activity_open_enter_legacy.xml b/core/res/res/anim/activity_open_enter_legacy.xml
new file mode 100644
index 0000000..38d3e8e
--- /dev/null
+++ b/core/res/res/anim/activity_open_enter_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <alpha
+ android:fromAlpha="0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="50"
+ android:duration="50"/>
+ <scale
+ android:fromXScale="0.85"
+ android:toXScale="1"
+ android:fromYScale="0.85"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml
index 3865d21..372f2c8 100644
--- a/core/res/res/anim/activity_open_exit.xml
+++ b/core/res/res/anim/activity_open_exit.xml
@@ -19,27 +19,36 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <!-- Fade out, over a black surface, which simulates a black scrim -->
<alpha
- android:fromAlpha="1"
- android:toAlpha="0.4"
+ android:fromAlpha="1.0"
+ android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="83"
- android:duration="167"/>
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="450" />
- <scale
- android:fromXScale="1"
- android:toXScale="1.05"
- android:fromYScale="1"
- android:toYScale="1.05"
- android:pivotX="50%"
- android:pivotY="50%"
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="-10%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400"/>
+ android:startOffset="0"
+ android:duration="450" />
+
+ <extend
+ android:fromExtendLeft="0"
+ android:fromExtendTop="0"
+ android:fromExtendRight="10%"
+ android:fromExtendBottom="0"
+ android:toExtendLeft="0"
+ android:toExtendTop="0"
+ android:toExtendRight="10%"
+ android:toExtendBottom="0"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_open_exit_legacy.xml b/core/res/res/anim/activity_open_exit_legacy.xml
new file mode 100644
index 0000000..3865d21
--- /dev/null
+++ b/core/res/res/anim/activity_open_exit_legacy.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <!-- Fade out, over a black surface, which simulates a black scrim -->
+ <alpha
+ android:fromAlpha="1"
+ android:toAlpha="0.4"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="83"
+ android:duration="167"/>
+
+ <scale
+ android:fromXScale="1"
+ android:toXScale="1.05"
+ android:fromYScale="1"
+ android:toYScale="1.05"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="400"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml
deleted file mode 100644
index 083a7aa..0000000
--- a/core/res/res/drawable-car/car_checkbox.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:width="@*android:dimen/car_primary_icon_size"
- android:height="@*android:dimen/car_primary_icon_size"
- android:drawable="@drawable/btn_check_material_anim"/>
- <item
- android:width="@*android:dimen/car_primary_icon_size"
- android:height="@*android:dimen/car_primary_icon_size"
- android:drawable="@drawable/car_checkbox_background"/>
-</layer-list>
diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml
deleted file mode 100644
index 69dcdbb..0000000
--- a/core/res/res/drawable-car/car_checkbox_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="#8A0041BE" />
- <stroke android:width="4dp" android:color="#0041BE" />
- </shape>
- </item>
- <item android:state_focused="true">
- <shape android:shape="rectangle">
- <solid android:color="#3D0059B3" />
- <stroke android:width="8dp" android:color="#0059B3" />
- </shape>
- </item>
-</selector>
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
new file mode 100644
index 0000000..cce9593
--- /dev/null
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -0,0 +1,115 @@
+<?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.
+-->
+
+<!-- NOTE: outer layout is required to provide proper shadow. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/autofill_dialog_picker"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/autofill_save_outer_top_margin"
+ android:padding="@dimen/autofill_save_outer_top_padding"
+ android:elevation="@dimen/autofill_elevation"
+ android:background="?android:attr/colorBackground"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/autofill_service_icon"
+ android:scaleType="fitStart"
+ android:visibility="gone"
+ android:layout_width="@dimen/autofill_dialog_icon_size"
+ android:layout_height="@dimen/autofill_dialog_icon_size"/>
+
+ <LinearLayout
+ android:id="@+id/autofill_dialog_header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:visibility="gone"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/autofill_dialog_container"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:visibility="gone"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+
+ <ListView
+ android:id="@+id/autofill_dialog_list"
+ android:layout_weight="1"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:drawSelectorOnTop="true"
+ android:clickable="true"
+ android:divider="@null"
+ android:visibility="gone"
+ android:paddingStart="@dimen/autofill_save_inner_padding"
+ android:paddingEnd="@dimen/autofill_save_inner_padding"
+ android:foreground="?attr/listChoiceBackgroundIndicator"
+ style="@style/AutofillDatasetPicker" />
+
+ <com.android.internal.widget.ButtonBarLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:padding="@dimen/autofill_save_button_bar_padding"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/autofill_dialog_no"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="@string/dismiss_action">
+ </Button>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible">
+ </Space>
+
+ <Button
+ android:id="@+id/autofill_dialog_yes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Widget.DeviceDefault.Button.Colored"
+ android:text="@string/autofill_save_yes"
+ android:visibility="gone" >
+ </Button>
+
+ </com.android.internal.widget.ButtonBarLayout>
+
+</LinearLayout>
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/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
new file mode 100644
index 0000000..44ed6f2
--- /dev/null
+++ b/core/res/res/layout/miniresolver.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.internal.widget.ResolverDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxWidth="@dimen/resolver_max_width"
+ android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
+ android:maxCollapsedHeightSmall="56dp"
+ android:id="@id/contentPanel">
+
+ <RelativeLayout
+ android:id="@+id/title_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alwaysShow="true"
+ android:elevation="@dimen/resolver_elevation"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingBottom="@dimen/resolver_title_padding_bottom"
+ android:background="@drawable/bottomsheet_background">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ />
+
+ <TextView
+ android:id="@+id/open_cross_profile"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/icon"
+ android:layout_centerHorizontal="true"
+ android:textColor="?android:textColorPrimary"
+ />
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/button_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alwaysShow="true"
+ android:orientation="vertical"
+ android:background="?attr/colorBackground"
+ android:layout_ignoreOffset="true">
+ <View
+ android:id="@+id/resolver_button_bar_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackground"
+ android:foreground="?attr/dividerVertical" />
+ <RelativeLayout
+ style="?attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_ignoreOffset="true"
+ android:layout_hasNestedScrollIndicator="true"
+ android:gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:layoutDirection="locale"
+ android:measureWithLargestChild="true"
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
+
+ <Button
+ android:id="@+id/use_same_profile_browser"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:maxLines="2"
+ style="@android:style/Widget.DeviceDefault.Button.Borderless"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
+ android:text="@string/activity_resolver_use_once"
+ />
+
+ <Button
+ android:id="@+id/button_open"
+ android:layout_width="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:maxLines="2"
+ style="@android:style/Widget.DeviceDefault.Button.Colored"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
+ android:layout_height="wrap_content"
+ android:text="@string/whichViewApplicationLabel"
+ />
+ </RelativeLayout>
+ </LinearLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
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..8cd3ef1 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..e3c20ba 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 bf02452..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..d774fd4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2885,7 +2885,7 @@
<code>public void sayHello(View v)</code> method of your context
(typically, your Activity).
{@deprecated View actually traverses the Context
- hierarchy looking for the relevant method, which is fragile (an intermediate
+ hierarchy looking for the relevant method, which is fragile (an intermediate
ContextWrapper adding a same-named method would change behavior) and restricts
bytecode optimizers such as R8. Instead, use View.setOnClickListener.}-->
<attr name="onClick" format="string" />
@@ -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. -->
@@ -6945,10 +6953,10 @@
</declare-styleable>
<declare-styleable name="TranslateAnimation">
- <attr name="fromXDelta" format="float|fraction" />
- <attr name="toXDelta" format="float|fraction" />
- <attr name="fromYDelta" format="float|fraction" />
- <attr name="toYDelta" format="float|fraction" />
+ <attr name="fromXDelta" format="float|fraction|dimension" />
+ <attr name="toXDelta" format="float|fraction|dimension" />
+ <attr name="fromYDelta" format="float|fraction|dimension" />
+ <attr name="toYDelta" format="float|fraction|dimension" />
</declare-styleable>
<declare-styleable name="AlphaAnimation">
@@ -6967,6 +6975,34 @@
<attr name="toBottom" format="fraction" />
</declare-styleable>
+ <!-- Defines the ExtendAnimation used to extend windows during animations -->
+ <declare-styleable name="ExtendAnimation">
+ <!-- Defines the amount a window should be extended outward from the left at
+ the start of the animation. -->
+ <attr name="fromExtendLeft" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the top at
+ the start of the animation. -->
+ <attr name="fromExtendTop" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the right at
+ the start of the animation. -->
+ <attr name="fromExtendRight" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the bottom at
+ the start of the animation. -->
+ <attr name="fromExtendBottom" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the left by
+ the end of the animation by transitioning from the fromExtendLeft amount. -->
+ <attr name="toExtendLeft" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the top by
+ the end of the animation by transitioning from the fromExtendTop amount. -->
+ <attr name="toExtendTop" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the right by
+ the end of the animation by transitioning from the fromExtendRight amount. -->
+ <attr name="toExtendRight" format="float|fraction" />
+ <!-- Defines the amount a window should be extended outward from the bottom by
+ the end of the animation by transitioning from the fromExtendBottom amount. -->
+ <attr name="toExtendBottom" format="float|fraction" />
+ </declare-styleable>
+
<declare-styleable name="LayoutAnimation">
<!-- Fraction of the animation duration used to delay the beginning of
the animation of each child. -->
@@ -7819,7 +7855,7 @@
<!-- Name of a method on the Context used to inflate the menu that will be
called when the item is clicked.
- {@deprecated Menu actually traverses the Context hierarchy looking for the
+ {@deprecated Menu actually traverses the Context hierarchy looking for the
relevant method, which is fragile (an intermediate ContextWrapper adding a
same-named method would change behavior) and restricts bytecode optimizers
such as R8. Instead, use MenuItem.setOnMenuItemClickListener.} -->
@@ -8839,6 +8875,22 @@
<attr name="gameSessionService" format="string" />
</declare-styleable>
+ <!-- Use <code>game-mode-config</code> as the root tag of the XML resource that
+ describes a GameModeConfig.
+ Described here are the attributes that can be included in that tag. -->
+ <declare-styleable name="GameModeConfig">
+ <!-- Set true to opt in BATTERY mode. -->
+ <attr name="supportsBatteryGameMode" format="boolean" />
+ <!-- Set true to opt in PERFORMANCE mode. -->
+ <attr name="supportsPerformanceGameMode" format="boolean" />
+ <!-- Set true to enable ANGLE. -->
+ <attr name="allowGameAngleDriver" format="boolean" />
+ <!-- Set true to allow resolution downscaling intervention. -->
+ <attr name="allowGameDownscaling" format="boolean" />
+ <!-- Set true to allow FPS override intervention. -->
+ <attr name="allowGameFpsOverride" format="boolean" />
+ </declare-styleable>
+
<!-- Use <code>voice-enrollment-application</code>
as the root tag of the XML resource that escribes the supported keyphrases (hotwords)
by the enrollment application.
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3a2fb6e..cb40e86 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2839,6 +2839,14 @@
<attr name="path" />
<attr name="minSdkVersion" />
<attr name="maxSdkVersion" />
+ <!-- The order in which the apex system services are initiated. When there are dependencies
+ among apex system services, setting this attribute for each of them ensures that they are
+ created in the order required by those dependencies. The apex-system-services that are
+ started manually within SystemServer ignore the initOrder and are not considered for
+ automatic starting of the other services.
+ The value is a simple integer, with higher number being initialized first. If not specified,
+ the default order is 0. -->
+ <attr name="initOrder" format="integer" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5fee1fa..53cf463 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2373,6 +2373,15 @@
<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>
+ <!-- The list of supported dream complications -->
+ <integer-array name="config_supportedDreamComplications">
+ </integer-array>
+ <!-- The list of dream complications which should be enabled by default -->
+ <integer-array name="config_dreamComplicationsEnabledByDefault">
+ </integer-array>
<!-- Are we allowed to dream while not plugged in? -->
<bool name="config_dreamsEnabledOnBattery">false</bool>
@@ -2609,6 +2618,10 @@
will be locked. -->
<bool name="config_multiuserDelayUserDataLocking">false</bool>
+ <!-- Whether to automatically switch a non-primary user back to the primary user after a
+ timeout when the device is docked. -->
+ <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+
<!-- Whether to only install system packages on a user if they're allowlisted for that user
type. These are flags and can be freely combined.
0 - disable allowlist (install all system packages; no logging)
@@ -2704,6 +2717,9 @@
Values are bandwidth_estimator, carrier_config and modem. -->
<string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
+ <!-- Whether force to enable telephony new data stack or not -->
+ <bool name="config_force_enable_telephony_new_data_stack">false</bool>
+
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
Here are a few of them:
@@ -2881,6 +2897,11 @@
<string name="config_sensorUseStartedActivity" translatable="false"
>com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string>
+ <!-- Component name of the activity used to ask a user to confirm system language change after
+ receiving <Set Menu Language> CEC message. -->
+ <string name="config_hdmiCecSetMenuLanguageActivity"
+ >com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string>
+
<!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
<string name="config_platformVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>
@@ -4097,6 +4118,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
@@ -4149,6 +4179,15 @@
<string name="config_defaultMusicRecognitionService" translatable="false"></string>
+ <!-- The package name for the system's wallpaper effects generation service.
+ This service returns wallpaper effects 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, wallpaper effects
+ generation service will be disabled.
+ Example: "com.android.intelligence/.WallpaperEffectsGenerationService"
+-->
+ <string name="config_defaultWallpaperEffectsGenerationService" translatable="false"></string>
+
<!-- The package name for the default retail demo app.
This package must be trusted, as it has the permissions to query the usage stats on the
device.
@@ -4959,6 +4998,10 @@
<!-- URI used for Nearby Share SliceProvider scanning. -->
<string translatable="false" name="config_defaultNearbySharingSliceUri"></string>
+ <!-- Component name that accepts settings intents for saved devices.
+ Used by FastPairSettingsFragment. -->
+ <string translatable="false" name="config_defaultNearbyFastPairSettingsDevicesComponent"></string>
+
<!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing
check after reboot or airplane mode toggling -->
<bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool>
@@ -5121,6 +5164,9 @@
If given value is outside of this range, the option 1 (center) is assummed. -->
<integer name="config_letterboxDefaultPositionForReachability">1</integer>
+ <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
+ <bool name="config_letterboxIsEducationEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
@@ -5236,6 +5282,12 @@
<bool name="config_cecTvSendStandbyOnSleepDisabled_allowed">true</bool>
<bool name="config_cecTvSendStandbyOnSleepDisabled_default">false</bool>
+ <bool name="config_cecSetMenuLanguage_userConfigurable">true</bool>
+ <bool name="config_cecSetMenuLanguageEnabled_allowed">true</bool>
+ <bool name="config_cecSetMenuLanguageEnabled_default">true</bool>
+ <bool name="config_cecSetMenuLanguageDisabled_allowed">true</bool>
+ <bool name="config_cecSetMenuLanguageDisabled_default">false</bool>
+
<bool name="config_cecRcProfileTv_userConfigurable">true</bool>
<bool name="config_cecRcProfileTvNone_allowed">true</bool>
<bool name="config_cecRcProfileTvNone_default">true</bool>
@@ -5612,4 +5664,23 @@
<!-- Whether or not to enable the lock screen entry point for the QR code scanner. -->
<bool name="config_enableQrCodeScannerOnLockScreen">false</bool>
+
+ <!-- Whether Low Power Standby is supported and can be enabled. -->
+ <bool name="config_lowPowerStandbySupported">false</bool>
+
+ <!-- If supported, whether Low Power Standby is enabled by default. -->
+ <bool name="config_lowPowerStandbyEnabledByDefault">false</bool>
+
+ <!-- The amount of time after becoming non-interactive (in ms) after which
+ Low Power Standby can activate. -->
+ <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer>
+
+
+ <!-- Mapping to select an Intent.EXTRA_DOCK_STATE value from extcon state
+ key-value pairs. Each entry is evaluated in order and is of the form:
+ "[EXTRA_DOCK_STATE value],key1=value1,key2=value2[,...]"
+ An entry with no key-value pairs is valid and can be used as a wildcard.
+ -->
+ <string-array name="config_dockExtconStateMapping">
+ </string-array>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d374b74..4874e65 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>
@@ -877,6 +877,9 @@
<!-- Maximum number of datasets that are visible in the UX picker without scrolling -->
<integer name="autofill_max_visible_datasets">3</integer>
+ <!-- Size of an icon in the Autolfill fill dialog -->
+ <dimen name="autofill_dialog_icon_size">56dp</dimen>
+
<!-- Size of a slice shortcut view -->
<dimen name="slice_shortcut_size">56dp</dimen>
<!-- Size of action icons in a slice -->
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..d57f5ba 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3255,10 +3255,24 @@
<public name="showClockAndComplications" />
<!-- @hide @SystemApi -->
<public name="gameSessionService" />
+ <public name="supportsBatteryGameMode" />
+ <public name="supportsPerformanceGameMode" />
+ <public name="allowGameAngleDriver" />
+ <public name="allowGameDownscaling" />
+ <public name="allowGameFpsOverride" />
<public name="localeConfig" />
<public name="showBackground" />
<public name="inheritKeyStoreKeys" />
<public name="preferKeepClear" />
+ <public name="autoHandwritingEnabled" />
+ <public name="fromExtendLeft" />
+ <public name="fromExtendTop" />
+ <public name="fromExtendRight" />
+ <public name="fromExtendBottom" />
+ <public name="toExtendLeft" />
+ <public name="toExtendTop" />
+ <public name="toExtendRight" />
+ <public name="toExtendBottom" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
@@ -3267,6 +3281,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..47b4d38 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>
@@ -874,9 +876,9 @@
<string name="permgroupdesc_sms">send and view SMS messages</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgrouplab_storage">Files and media</string>
+ <string name="permgrouplab_storage">Files & documents</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
+ <string name="permgroupdesc_storage">access files and documents on your device</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
<string name="permgrouplab_readMediaAural">Music & other audio</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>
@@ -3870,6 +3872,11 @@
<!-- Message of notification shown when serial console is enabled. [CHAR LIMIT=NONE] -->
<string name="console_running_notification_message">Performance is impacted. To disable, check bootloader.</string>
+ <!-- Title of notification shown when MTE status override is enabled. [CHAR LIMIT=NONE] -->
+ <string name="mte_override_notification_title">Experimental MTE enabled</string>
+ <!-- Message of notification shown when MTE status override is enabled. [CHAR LIMIT=NONE] -->
+ <string name="mte_override_notification_message">Performance and stability might be impacted. Reboot to disable. If enabled using arm64.memtag.bootctl, set it to "none" beforehand.</string>
+
<!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
<string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
<!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
@@ -4228,12 +4235,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 +4601,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 +5136,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 +5229,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 +5405,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 +5567,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] -->
@@ -5707,6 +5705,20 @@
<!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
<string name="harmful_app_warning_title">Harmful app detected</string>
+ <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] -->
+ <string name="log_access_confirmation_title">System log access request</string>
+ <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="log_access_confirmation_allow">Only this time</string>
+ <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+ <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+ <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+ <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging.
+ These logs might contain information that apps and services on your device have written.</string>
+
+ <!-- Privacy notice do not show [CHAR LIMIT=20] -->
+ <string name="log_access_do_not_show_again">Don\u2019t show again</string>
+
<!-- Text describing a permission request for one app to show another app's
slices [CHAR LIMIT=NONE] -->
<string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
@@ -5843,10 +5855,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>
@@ -5874,6 +5887,8 @@
<string name="accessibility_system_action_lock_screen_label">Lock Screen</string>
<!-- Label for taking screenshot action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_screenshot_label">Screenshot</string>
+ <!-- Label for headset hook action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_headset_hook_label">Headset Hook</string>
<!-- Label for triggering on-screen accessibility shortcut action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_on_screen_a11y_shortcut_label">On-screen Accessibility Shortcut</string>
<!-- Label for showing on-screen accessibility shortcut chooser action [CHAR LIMIT=NONE] -->
@@ -5882,6 +5897,16 @@
<string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
<!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
+ <!-- Label for Dpad up action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_up_label">Dpad Up</string>
+ <!-- Label for Dpad down action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_down_label">Dpad Down</string>
+ <!-- Label for Dpad left action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_left_label">Dpad Left</string>
+ <!-- Label for Dpad right action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_right_label">Dpad Right</string>
+ <!-- Label for Dpad center action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dpad_center_label">Dpad Center</string>
<!-- Accessibility description of caption view -->
<string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
@@ -5941,9 +5966,9 @@
<string name="resolver_no_personal_apps_available">No personal apps</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
- <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
+ <string name="miniresolver_open_in_personal">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your personal profile?</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
- <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string>
+ <string name="miniresolver_open_in_work">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your work profile?</string>
<!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_use_personal_browser">Use personal browser</string>
<!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
@@ -6226,4 +6251,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..33efbce 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,6 +375,7 @@
<java-symbol type="string" name="config_usbConfirmActivity" />
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
+ <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -465,6 +466,7 @@
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+ <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -472,6 +474,7 @@
<java-symbol type="integer" name="config_mobile_mtu" />
<java-symbol type="array" name="config_mobile_tcp_buffers" />
<java-symbol type="string" name="config_tcp_buffers" />
+ <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
<java-symbol type="integer" name="config_volte_replacement_rat"/>
<java-symbol type="integer" name="config_valid_wappush_index" />
<java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -1251,13 +1254,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" />
@@ -1597,6 +1599,13 @@
<java-symbol type="layout" name="resolver_list_per_profile" />
<java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="layout" name="resolver_empty_states" />
+ <java-symbol type="id" name="open_cross_profile" />
+ <java-symbol type="string" name="miniresolver_open_in_personal" />
+ <java-symbol type="string" name="miniresolver_open_in_work" />
+ <java-symbol type="string" name="miniresolver_use_personal_browser" />
+ <java-symbol type="string" name="miniresolver_use_work_browser" />
+ <java-symbol type="id" name="button_open" />
+ <java-symbol type="id" name="use_same_profile_browser" />
<java-symbol type="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />
@@ -1695,7 +1704,13 @@
<java-symbol type="anim" name="activity_translucent_open_enter" />
<java-symbol type="anim" name="activity_translucent_close_exit" />
<java-symbol type="anim" name="activity_open_enter" />
+ <java-symbol type="anim" name="activity_open_exit" />
+ <java-symbol type="anim" name="activity_close_enter" />
<java-symbol type="anim" name="activity_close_exit" />
+ <java-symbol type="anim" name="activity_open_enter_legacy" />
+ <java-symbol type="anim" name="activity_open_exit_legacy" />
+ <java-symbol type="anim" name="activity_close_enter_legacy" />
+ <java-symbol type="anim" name="activity_close_exit_legacy" />
<java-symbol type="anim" name="task_fragment_close_enter" />
<java-symbol type="anim" name="task_fragment_close_exit" />
<java-symbol type="anim" name="task_fragment_open_enter" />
@@ -2081,6 +2096,8 @@
<java-symbol type="string" name="test_harness_mode_notification_message" />
<java-symbol type="string" name="console_running_notification_title" />
<java-symbol type="string" name="console_running_notification_message" />
+ <java-symbol type="string" name="mte_override_notification_title" />
+ <java-symbol type="string" name="mte_override_notification_message" />
<java-symbol type="string" name="taking_remote_bugreport_notification_title" />
<java-symbol type="string" name="share_remote_bugreport_notification_title" />
<java-symbol type="string" name="sharing_remote_bugreport_notification_title" />
@@ -2211,7 +2228,10 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
+ <java-symbol type="array" name="config_supportedDreamComplications" />
+ <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" />
<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 +2522,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" />
@@ -2702,6 +2722,7 @@
<java-symbol type="bool" name="config_allow_ussd_over_ims" />
<java-symbol type="attr" name="touchscreenBlocksFocus" />
<java-symbol type="layout" name="resolver_list_with_default" />
+ <java-symbol type="layout" name="miniresolver" />
<java-symbol type="string" name="activity_resolver_use_always" />
<java-symbol type="string" name="whichApplicationNamed" />
<java-symbol type="string" name="whichApplicationLabel" />
@@ -2941,7 +2962,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 +3132,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" />
@@ -3506,6 +3526,7 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+ <java-symbol type="layout" name="autofill_fill_dialog"/>
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
@@ -3518,9 +3539,16 @@
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_yes" />
+ <java-symbol type="id" name="autofill_service_icon" />
+ <java-symbol type="id" name="autofill_dialog_picker"/>
+ <java-symbol type="id" name="autofill_dialog_header"/>
+ <java-symbol type="id" name="autofill_dialog_container"/>
+ <java-symbol type="id" name="autofill_dialog_list"/>
+ <java-symbol type="id" name="autofill_dialog_no" />
+ <java-symbol type="id" name="autofill_dialog_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 +3664,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" />
@@ -3657,6 +3686,7 @@
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
<java-symbol type="string" name="config_defaultSearchUiService" />
<java-symbol type="string" name="config_defaultSmartspaceService" />
+ <java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultRotationResolverService" />
@@ -3832,6 +3862,11 @@
<java-symbol type="string" name="harmful_app_warning_title" />
<java-symbol type="layout" name="harmful_app_warning_dialog" />
+ <java-symbol type="string" name="log_access_confirmation_allow" />
+ <java-symbol type="string" name="log_access_confirmation_deny" />
+ <java-symbol type="string" name="log_access_confirmation_title" />
+ <java-symbol type="string" name="log_access_confirmation_body" />
+
<java-symbol type="string" name="config_defaultAssistantAccessComponent" />
<java-symbol type="string" name="slices_permission_request" />
@@ -4085,6 +4120,7 @@
<java-symbol type="layout" name="chooser_action_button" />
<java-symbol type="dimen" name="chooser_action_button_icon_size" />
<java-symbol type="string" name="config_defaultNearbySharingComponent" />
+ <java-symbol type="string" name="config_defaultNearbyFastPairSettingsDevicesComponent" />
<java-symbol type="bool" name="config_disable_all_cb_messages" />
<java-symbol type="drawable" name="ic_close" />
@@ -4112,10 +4148,16 @@
<java-symbol type="string" name="accessibility_system_action_quick_settings_label" />
<java-symbol type="string" name="accessibility_system_action_recents_label" />
<java-symbol type="string" name="accessibility_system_action_screenshot_label" />
+ <java-symbol type="string" name="accessibility_system_action_headset_hook_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
<java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_up_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_down_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_left_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_right_label" />
+ <java-symbol type="string" name="accessibility_system_action_dpad_center_label" />
<java-symbol type="string" name="accessibility_freeform_caption" />
@@ -4315,6 +4357,7 @@
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+ <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4435,6 +4478,12 @@
<java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_allowed" />
<java-symbol type="bool" name="config_cecTvSendStandbyOnSleepDisabled_default" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguage_userConfigurable" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_allowed" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageEnabled_default" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_allowed" />
+ <java-symbol type="bool" name="config_cecSetMenuLanguageDisabled_default" />
+
<java-symbol type="bool" name="config_cecRcProfileTv_userConfigurable" />
<java-symbol type="bool" name="config_cecRcProfileTvNone_allowed" />
<java-symbol type="bool" name="config_cecRcProfileTvNone_default" />
@@ -4661,4 +4710,16 @@
<java-symbol type="string" name="config_deviceManagerUpdater" />
<java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
+
+ <java-symbol type="array" name="config_dockExtconStateMapping" />
+
+ <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"/>
+
+ <java-symbol type="bool" name="config_lowPowerStandbySupported" />
+ <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" />
+ <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" />
</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/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
index bd987a0..6639c02 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<application
android:theme="@style/Theme"
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/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
new file mode 100644
index 0000000..713a4c8
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
new file mode 100644
index 0000000..74c939b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/themed_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/RelativeLayoutAlignTop25Alpha"/>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
similarity index 65%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
copy to core/tests/coretests/res/layout/remote_views_light_background_text.xml
index 16dea48..f300f09 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
+++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2016 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -12,9 +12,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@android:color/system_neutral2_200">
- <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
-</ripple>
\ No newline at end of file
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/light_background_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/core/tests/coretests/res/layout/remote_views_list.xml
similarity index 65%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
copy to core/tests/coretests/res/layout/remote_views_list.xml
index 16dea48..ca43bc8 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
+++ b/core/tests/coretests/res/layout/remote_views_list.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2016 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -12,9 +12,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@android:color/system_neutral2_200">
- <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
-</ripple>
\ No newline at end of file
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 352b4dc..32eebb35 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -34,6 +34,16 @@
<style name="LayoutInDisplayCutoutModeAlways">
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
</style>
+ <style name="RelativeLayoutAlignBottom50Alpha">
+ <item name="android:layout_alignParentTop">false</item>
+ <item name="android:layout_alignParentBottom">true</item>
+ <item name="android:alpha">0.5</item>
+ </style>
+ <style name="RelativeLayoutAlignTop25Alpha">
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:layout_alignParentBottom">false</item>
+ <item name="android:alpha">0.25</item>
+ </style>
<style name="WindowBackgroundColorLiteral">
<item name="android:windowBackground">#00FF00</item>
</style>
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index d3e8bb0..5338d04 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -37,9 +37,9 @@
@SmallTest
public class PropertyInvalidatedCacheTests {
- // This property is never set. The test process does not have permission to set any
- // properties.
- static final String CACHE_PROPERTY = "cache_key.cache_test_a";
+ // Configuration for creating caches
+ private static final int MODULE = PropertyInvalidatedCache.MODULE_TEST;
+ private static final String API = "testApi";
// This class is a proxy for binder calls. It contains a counter that increments
// every time the class is queried.
@@ -64,6 +64,25 @@
}
}
+ // The functions for querying the server.
+ private static class ServerQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> {
+ private final ServerProxy mServer;
+
+ ServerQuery(ServerProxy server) {
+ mServer = server;
+ }
+
+ @Override
+ public Boolean apply(Integer x) {
+ return mServer.query(x);
+ }
+ @Override
+ public boolean shouldBypassCache(Integer x) {
+ return x % 13 == 0;
+ }
+ }
+
// Clear the test mode after every test, in case this process is used for other
// tests. This also resets the test property map.
@After
@@ -82,19 +101,11 @@
// Create a cache that uses simple arithmetic to computer its values.
PropertyInvalidatedCache<Integer, Boolean> testCache =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- @Override
- public boolean bypass(Integer x) {
- return x % 13 == 0;
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache.setTestMode(true);
- PropertyInvalidatedCache.testPropertyName(CACHE_PROPERTY);
+ testCache.testPropertyName();
tester.verify(0);
assertEquals(tester.value(3), testCache.query(3));
@@ -136,26 +147,14 @@
// Three caches, all using the same system property but one uses a different name.
PropertyInvalidatedCache<Integer, Boolean> cache1 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache2 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache3 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache3",
+ new ServerQuery(tester));
// Caches are enabled upon creation.
assertEquals(false, cache1.getDisabledState());
@@ -176,45 +175,29 @@
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(true, cache1.getDisabledState());
// Remove the record of caches being locally disabled. This is a clean-up step.
- cache1.clearDisableLocal();
+ cache1.forgetDisableLocal();
assertEquals(true, cache1.getDisabledState());
assertEquals(true, cache2.getDisabledState());
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is not disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(false, cache1.getDisabledState());
}
- private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
+ private static class TestQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, String> {
- private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
- TestCache() {
- this(CACHE_PROPERTY);
- }
-
- TestCache(String key) {
- super(4, key);
- setTestMode(true);
- testPropertyName(key);
- }
+ private int mRecomputeCount = 0;
@Override
- public String recompute(Integer qv) {
+ public String apply(Integer qv) {
mRecomputeCount += 1;
return "foo" + qv.toString();
}
@@ -222,15 +205,40 @@
int getRecomputeCount() {
return mRecomputeCount;
}
+ }
- private int mRecomputeCount = 0;
+ private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
+ private final TestQuery mQuery;
+
+ TestCache() {
+ this(MODULE, API);
+ }
+
+ TestCache(int module, String api) {
+ this(module, api, new TestQuery());
+ setTestMode(true);
+ testPropertyName();
+ }
+
+ TestCache(int module, String api, TestQuery query) {
+ super(4, module, api, api, query);
+ mQuery = query;
+ setTestMode(true);
+ testPropertyName();
+ }
+
+ public int getRecomputeCount() {
+ return mQuery.getRecomputeCount();
+ }
+
+
}
@Test
public void testCacheRecompute() {
TestCache cache = new TestCache();
cache.invalidateCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
@@ -241,6 +249,11 @@
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
+ // Invalidate the cache with a direct call to the property.
+ PropertyInvalidatedCache.invalidateCache(MODULE, API);
+ assertEquals("foo5", cache.query(5));
+ assertEquals("foo5", cache.query(5));
+ assertEquals(4, cache.getRecomputeCount());
}
@Test
@@ -257,7 +270,8 @@
@Test
public void testCachePropertyUnset() {
- TestCache cache = new TestCache(UNSET_KEY);
+ final String UNSET_API = "otherApi";
+ TestCache cache = new TestCache(MODULE, UNSET_API);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(2, cache.getRecomputeCount());
@@ -327,17 +341,40 @@
@Test
public void testLocalProcessDisable() {
TestCache cache = new TestCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.invalidateCache();
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.disableLocal();
- assertEquals(cache.isDisabledLocal(), true);
+ assertEquals(cache.isDisabled(), true);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
}
+
+ @Test
+ public void testPropertyNames() {
+ String n1;
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ try {
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM - 1, "get package_info");
+ // n1 is an invalid api name.
+ assertEquals(false, true);
+ } catch (IllegalArgumentException e) {
+ // An exception is expected here.
+ }
+
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+ assertEquals(n1, "cache_key.bluetooth.get_state");
+ }
}
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..5c9044c 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 */);
@@ -669,6 +646,10 @@
}
@Override
+ public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ }
+
+ @Override
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
}
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/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index b66642c..fa657f7 100644
--- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -71,6 +71,8 @@
private Resources mResources;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Drawable mDrawable;
+ @Mock
+ private PackageManager mPackageManager;
private CrossProfileApps mCrossProfileApps;
@Before
@@ -87,6 +89,7 @@
Context.DEVICE_POLICY_SERVICE);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
}
@Before
@@ -131,7 +134,8 @@
setValidTargetProfile(MANAGED_PROFILE);
mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE);
- verify(mResources).getDrawable(R.drawable.ic_corp_badge, null);
+ verify(mPackageManager).getUserBadgeForDensityNoBackground(
+ MANAGED_PROFILE, /* density= */0);
}
@Test
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
similarity index 85%
rename from core/tests/coretests/src/android/content/pm/PackageHelperTests.java
rename to core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
index 947da0b..0629a99 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java
@@ -25,7 +25,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import org.mockito.Mockito;
@@ -36,10 +36,9 @@
import java.util.UUID;
@Presubmit
-public class PackageHelperTests extends AndroidTestCase {
+public class InstallLocationUtilsTests extends AndroidTestCase {
private static final boolean localLOGV = true;
public static final String TAG = "PackageHelperTests";
- protected final String PREFIX = "android.content.pm";
private static final String sInternalVolPath = "/data";
private static final String sAdoptedVolPath = "/mnt/expand/123";
@@ -88,11 +87,14 @@
UUID internalUuid = UUID.randomUUID();
UUID adoptedUuid = UUID.randomUUID();
UUID publicUuid = UUID.randomUUID();
- Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
+ Mockito.when(storageManager.getStorageBytesUntilLow(internalFile))
+ .thenReturn(sInternalSize);
Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
- Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))).thenReturn(internalUuid);
- Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))).thenReturn(adoptedUuid);
+ Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile)))
+ .thenReturn(internalUuid);
+ Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile)))
+ .thenReturn(adoptedUuid);
Mockito.when(storageManager.getUuidForPath(Mockito.eq(publicFile))).thenReturn(publicUuid);
Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalUuid), Mockito.anyInt()))
.thenReturn(sInternalSize);
@@ -103,7 +105,7 @@
return storageManager;
}
- private static final class MockedInterface extends PackageHelper.TestableInterface {
+ private static final class MockedInterface extends InstallLocationUtils.TestableInterface {
private boolean mForceAllowOnExternal = false;
private boolean mAllow3rdPartyOnInternal = true;
private ApplicationInfo mApplicationInfo = null;
@@ -164,25 +166,25 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
@@ -192,7 +194,7 @@
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -202,7 +204,7 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -212,7 +214,7 @@
mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -222,7 +224,7 @@
mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch(IOException e) {
@@ -240,13 +242,13 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
}
@@ -260,25 +262,25 @@
appInfo.volumeUuid = sAdoptedVolUuid;
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
@@ -292,7 +294,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -302,7 +304,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -312,7 +314,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -322,7 +324,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
try {
- PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
fail("Expected exception was not thrown " + appInfo.volumeUuid);
} catch (IOException e) {
@@ -336,28 +338,28 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sAdoptedVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the volume with bigger available space.
assertEquals(sAdoptedVolUuid, volume);
@@ -371,20 +373,20 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
true /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
true /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sInternalVolUuid, volume);
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch (IOException e) {
@@ -395,7 +397,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
@@ -407,7 +409,7 @@
mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
false /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the non-internal volume.
assertEquals(sAdoptedVolUuid, volume);
@@ -415,7 +417,7 @@
appInfo = null;
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
// Should return the non-internal volume.
assertEquals(sAdoptedVolUuid, volume);
@@ -428,7 +430,7 @@
true /*allow 3rd party on internal*/);
String volume = null;
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal ONLY*/,
1000000 /*size too big*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
@@ -445,7 +447,7 @@
false /*allow 3rd party on internal*/);
String volume = null;
try {
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
fail("Expected exception in resolveInstallVolume was not thrown");
} catch (IOException e) {
@@ -456,7 +458,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
@@ -474,7 +476,7 @@
mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
false /*allow 3rd party on internal*/);
String volume = null;
- volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+ volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name",
1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
assertEquals(sAdoptedVolUuid, volume);
}
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/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index 104f077..f7ca822 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -146,6 +146,7 @@
@Test
public void testValidateWaveformBuilder() {
+ // Cover builder methods
VibrationEffect.startWaveform(targetAmplitude(1))
.addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
.addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
@@ -158,6 +159,39 @@
.build()
.validate();
+ // Make sure class summary javadoc examples compile and are valid.
+ // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs.
+ VibrationEffect.startWaveform(targetFrequency(60))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ .addSustain(Duration.ofMillis(200))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ .build()
+ .validate();
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addOffDuration(Duration.ofMillis(20))
+ .repeatEffectIndefinitely(
+ VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ .addSustain(Duration.ofMillis(10))
+ .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
+ .addSustain(Duration.ofMillis(30))
+ .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
+ .addSustain(Duration.ofMillis(50))
+ .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
+ .build())
+ .compose()
+ .validate();
+ VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1)
+ .validate();
+ VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ .addSustain(Duration.ofMillis(10))
+ .addTransition(Duration.ZERO, targetAmplitude(0.4f))
+ .addSustain(Duration.ofMillis(20))
+ .addTransition(Duration.ZERO, targetAmplitude(0.8f))
+ .addSustain(Duration.ofMillis(30))
+ .build()
+ .validate();
+
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startWaveform().build().validate());
assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
@@ -171,6 +205,7 @@
@Test
public void testValidateComposed() {
+ // Cover builder methods
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.addEffect(TEST_ONE_SHOT)
@@ -178,11 +213,28 @@
.addOffDuration(Duration.ofMillis(100))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
+ .compose()
+ .validate();
+ VibrationEffect.startComposition()
+ .repeatEffectIndefinitely(TEST_ONE_SHOT)
.compose()
.validate();
+ // Make sure class summary javadoc examples compile and are valid.
+ // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs.
VibrationEffect.startComposition()
- .repeatEffectIndefinitely(TEST_ONE_SHOT)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
+ .compose()
+ .validate();
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ .addOffDuration(Duration.ofMillis(10))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ .addOffDuration(Duration.ofMillis(50))
+ .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0))
.compose()
.validate();
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/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 74cdd21..10f6f1f 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -16,3 +16,6 @@
# Scroll Capture
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+
+# Stylus
+per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
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/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
index 6df9002..ddc27aa 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -42,7 +42,7 @@
// and assertAccessibilityEventCleared
/** The number of properties of the {@link AccessibilityEvent} class. */
- private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 32;
+ private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 33;
// The number of fields tested in the corresponding CTS AccessibilityRecordTest:
// assertAccessibilityRecordCleared, fullyPopulateAccessibilityRecord,
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 2018836..99670d9 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -58,7 +58,7 @@
// The number of flags held in boolean properties. Their values should also be double-checked
// in the methods above.
- private static final int NUM_BOOLEAN_PROPERTIES = 23;
+ private static final int NUM_BOOLEAN_PROPERTIES = 24;
@Test
public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 02e5942..fc385a0 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -204,4 +204,6 @@
public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
int processId, long threadId, int callingUid, Bundle serializedCallingStackInBundle) {}
+
+ public void setAnimationScale(float scale) {}
}
diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
similarity index 78%
rename from core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
rename to core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 5ea9199..e11fe17 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -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,7 +14,7 @@
* limitations under the License.
*/
-package android.view;
+package android.view.stylus;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -33,6 +33,12 @@
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingInitiator;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,7 +53,7 @@
* Tests for {@link HandwritingInitiator}
*
* Build/Install/Run:
- * atest FrameworksCoreTests:HandwritingInitiatorTest
+ * atest FrameworksCoreTests:android.view.stylus.HandwritingInitiatorTest
*/
@Presubmit
@SmallTest
@@ -59,34 +65,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 +195,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 +238,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 +273,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/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 059c764..00b3693 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,8 +16,12 @@
package android.widget;
+import static com.android.internal.R.id.pending_intent_tag;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -31,7 +35,10 @@
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Looper;
import android.os.Parcel;
+import android.util.SizeF;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
@@ -49,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
/**
@@ -261,6 +269,148 @@
verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2");
}
+ @Test
+ public void nestedViews_setRemoteAdapter_intent() {
+ Looper.prepare();
+
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(R.id.list, new Intent());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ listView.onRemoteAdapterConnected();
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_setRemoteAdapter_remoteCollectionItems() {
+ AppWidgetHostView widget = new AppWidgetHostView(mContext);
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host);
+ RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list);
+ inner2.setRemoteAdapter(
+ R.id.list,
+ new RemoteViews.RemoteCollectionItems.Builder()
+ .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host))
+ .build());
+ inner1.addView(R.id.container, inner2);
+ top.addView(R.id.container, inner1);
+
+ View view = top.apply(mContext, widget);
+ widget.addView(view);
+
+ ListView listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+
+ top.reapply(mContext, view);
+ listView = (ListView) view.findViewById(R.id.list);
+ assertNotNull(listView.getAdapter());
+ }
+
+ @Test
+ public void nestedViews_collectionChildFlag() throws Exception {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
+ listItem.addView(R.id.container, nested);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void landscapePortraitViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(inner, inner);
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void sizedViews_collectionChildFlag() throws Exception {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setOnClickPendingIntent(
+ R.id.text,
+ PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ );
+
+ RemoteViews listItem = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+
+ View view = listItem.apply(mContext, mContainer);
+ TextView text = (TextView) view.findViewById(R.id.text);
+ assertNull(text.getTag(pending_intent_tag));
+ }
+
+ @Test
+ public void nestedViews_lightBackgroundLayoutFlag() {
+ RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
+ nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host);
+ parent.addView(R.id.container, nested);
+ parent.setLightBackgroundLayoutId(R.layout.remote_view_host);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+
+ @Test
+ public void landscapePortraitViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(inner, inner);
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
+ @Test
+ public void sizedViews_lightBackgroundLayoutFlag() {
+ RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
+ inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+
+ RemoteViews parent = new RemoteViews(
+ Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner));
+ parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+
+ View view = parent.apply(mContext, mContainer);
+ assertNull(view.findViewById(R.id.text));
+ assertNotNull(view.findViewById(R.id.light_background_text));
+ }
+
private RemoteViews createViewChained(int depth, String... texts) {
RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -483,6 +633,47 @@
index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag));
}
+ @Test
+ public void nestedViews_themesPropagateCorrectly() {
+ Context themedContext =
+ new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha);
+ RelativeLayout rootParent = new RelativeLayout(themedContext);
+
+ RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+ RemoteViews inner1 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme);
+ RemoteViews inner2 =
+ new RemoteViews(mPackage, R.layout.remote_view_relative_layout);
+
+ inner1.addView(R.id.themed_layout, inner2);
+ top.addView(R.id.container, inner1);
+
+ RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent);
+ assertEquals(0.5, root.getAlpha(), 0.);
+ RelativeLayout.LayoutParams rootParams =
+ (RelativeLayout.LayoutParams) root.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ // The theme is set on inner1View and its descendants. However, inner1View does
+ // not get its layout params from its theme (though its descendants do), but other
+ // attributes such as alpha are set.
+ RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0);
+ assertEquals(R.id.themed_layout, inner1View.getId());
+ assertEquals(0.25, inner1View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner1Params =
+ (RelativeLayout.LayoutParams) inner1View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM));
+
+ RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0);
+ assertEquals(0.25, inner2View.getAlpha(), 0.);
+ RelativeLayout.LayoutParams inner2Params =
+ (RelativeLayout.LayoutParams) inner2View.getLayoutParams();
+ assertEquals(RelativeLayout.TRUE,
+ inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP));
+ }
+
private class WidgetContainer extends AppWidgetHostView {
int[] mSharedViewIds;
String[] mSharedViewNames;
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
new file mode 100644
index 0000000..91d8531
--- /dev/null
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.app.ActivityTaskManager;
+import android.app.EmptyActivity;
+import android.app.Instrumentation;
+import android.os.RemoteException;
+import android.support.test.uiautomator.UiDevice;
+import android.view.OnBackInvokedCallback;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Integration test for back navigation
+ */
+public class BackNavigationTest {
+
+ @Rule
+ public final ActivityScenarioRule<EmptyActivity> mScenarioRule =
+ new ActivityScenarioRule<>(EmptyActivity.class);
+ private ActivityScenario<EmptyActivity> mScenario;
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setup() {
+ mScenario = mScenarioRule.getScenario();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ try {
+ UiDevice.getInstance(mInstrumentation).wakeUp();
+ } catch (RemoteException ignored) {
+ }
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
+ }
+
+ @Test
+ public void registerCallback_initialized() {
+ CountDownLatch latch = registerBackCallback();
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertCallbackIsCalled(latch);
+ }
+
+ @Test
+ public void registerCallback_created() {
+ mScenario.moveToState(Lifecycle.State.CREATED);
+ CountDownLatch latch = registerBackCallback();
+ mScenario.moveToState(Lifecycle.State.STARTED);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ assertCallbackIsCalled(latch);
+ }
+
+ @Test
+ public void registerCallback_resumed() {
+ mScenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.STARTED);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ CountDownLatch latch = registerBackCallback();
+ assertCallbackIsCalled(latch);
+ }
+
+ private void assertCallbackIsCalled(CountDownLatch latch) {
+ try {
+ mInstrumentation.getUiAutomation().waitForIdle(500, 1000);
+ BackNavigationInfo info = ActivityTaskManager.getService().startBackNavigation();
+ assertNotNull("BackNavigationInfo is null", info);
+ assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback());
+ info.getOnBackInvokedCallback().onBackInvoked();
+ assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ } catch (InterruptedException ex) {
+ fail("Application died before invoking the callback.\n" + ex.getMessage());
+ } catch (TimeoutException ex) {
+ fail(ex.getMessage());
+ }
+ }
+
+ @NonNull
+ private CountDownLatch registerBackCallback() {
+ CountDownLatch backInvokedLatch = new CountDownLatch(1);
+ CountDownLatch backRegisteredLatch = new CountDownLatch(1);
+ mScenario.onActivity(activity -> {
+ activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ new OnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {
+ backInvokedLatch.countDown();
+ }
+ }, 0
+ );
+ backRegisteredLatch.countDown();
+ });
+ try {
+ if (!backRegisteredLatch.await(100, TimeUnit.MILLISECONDS)) {
+ fail("Back callback was not registered on the Activity thread. This might be "
+ + "an error with the test itself.");
+ }
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ return backInvokedLatch;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 75d2025..2817728f 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -731,6 +731,25 @@
}
@Test
+ public void testMiniResolver() {
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ // Personal profile only has a browser
+ personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+ }
+
+ @Test
public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
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/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index b655369..f5cbffb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,7 @@
package com.android.internal.os;
import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
@@ -30,6 +31,12 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.telephony.Annotation;
+import android.telephony.CellSignalStrength;
+import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.Display;
@@ -1165,6 +1172,185 @@
"D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
}
+ @SmallTest
+ public void testGetPerStateActiveRadioDurationMs() {
+ final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+ final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+ final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+ final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+ final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+ for (int rat = 0; rat < ratCount; rat++) {
+ for (int freq = 0; freq < frequencyCount; freq++) {
+ for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+ expectedDurationsMs[rat][freq][txLvl] = 0;
+ }
+ }
+ }
+
+ class ModemAndBatteryState {
+ public long currentTimeMs = 100;
+ public boolean onBattery = false;
+ public boolean modemActive = false;
+ @Annotation.NetworkType
+ public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ @BatteryStats.RadioAccessTechnology
+ public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+ @ServiceState.FrequencyRange
+ public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+ public SparseIntArray currentSignalStrengths = new SparseIntArray();
+
+ void setOnBattery(boolean onBattery) {
+ this.onBattery = onBattery;
+ bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+ currentTimeMs * 1000);
+ }
+
+ void setModemActive(boolean active) {
+ modemActive = active;
+ final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+ }
+
+ void setRatType(@Annotation.NetworkType int dataType,
+ @BatteryStats.RadioAccessTechnology int rat) {
+ currentNetworkDataType = dataType;
+ currentRat = rat;
+ bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+ currentFrequencyRange);
+ }
+
+ void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+ currentFrequencyRange = frequency;
+ bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+ ServiceState.STATE_IN_SERVICE, frequency);
+ }
+
+ void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+ currentSignalStrengths.put(rat, strength);
+ final int size = currentSignalStrengths.size();
+ final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+ bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+ }
+ }
+ final ModemAndBatteryState state = new ModemAndBatteryState();
+
+ IntConsumer incrementTime = inc -> {
+ state.currentTimeMs += inc;
+ clock.realtime = clock.uptime = state.currentTimeMs;
+
+ // If the device is not on battery, no timers should increment.
+ if (!state.onBattery) return;
+ // If the modem is not active, no timers should increment.
+ if (!state.modemActive) return;
+
+ final int currentRat = state.currentRat;
+ final int currentFrequencyRange =
+ currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+ int currentSignalStrength = state.currentSignalStrengths.get(currentRat);
+ expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
+ };
+
+ state.setOnBattery(false);
+ state.setModemActive(false);
+ state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // While not on battery, the timers should not increase.
+ state.setModemActive(true);
+ incrementTime.accept(100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+ incrementTime.accept(300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+ incrementTime.accept(400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+ incrementTime.accept(500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+ // start counting up.
+ state.setOnBattery(true);
+ incrementTime.accept(600);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing LTE signal strength should be tracked.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(700);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ incrementTime.accept(800);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+ incrementTime.accept(900);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+ incrementTime.accept(1000);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Change in the signal strength of nonactive RAT should not affect anything.
+ state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+ CellSignalStrength.SIGNAL_STRENGTH_POOR);
+ incrementTime.accept(1100);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Changing to OTHER Rat should start tracking the poor signal strength.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+ BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+ incrementTime.accept(1200);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+ incrementTime.accept(1300);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+ state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+ incrementTime.accept(1400);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Noting frequency change should not affect non NR Rat.
+ state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ // Modem no longer active, should not be tracking any more.
+ state.setModemActive(false);
+ incrementTime.accept(1500);
+ checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
@@ -1238,4 +1424,30 @@
bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
}
}
+
+ private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
+ final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
+ for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+ final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
+ for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
+ final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+ final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
+ strength, currentTimeMs);
+
+ // Build a verbose fail message, just in case.
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Wrong time in state for RAT:");
+ sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+ sb.append(", frequency:");
+ sb.append(ServiceState.frequencyRangeToString(freq));
+ sb.append(", strength:");
+ sb.append(strength);
+
+ assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+ }
+ }
+ }
+ }
}
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/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index dbb2cf1..88349b3 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -383,10 +383,10 @@
assertEquals(13, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 0));
// 6 * (6000-4000)/(6000-2000)
assertEquals(3, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 1));
-
- // POWER_BUCKET_SCREEN_OTHER was only present along with state=1
- assertEquals(0, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 0));
- assertEquals(40, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 1));
+ // 40 * (4000-1000)/(5000-1000)
+ assertEquals(30, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 0));
+ // 40 * (5000-4000)/(5000-1000)
+ assertEquals(10, stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_OTHER, 1));
}
@Test
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index db63e6e..4c247427 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -38,7 +38,6 @@
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -155,7 +154,7 @@
verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
Mockito.reset(callback);
- mDeviceStateManagerGlobal.cancelRequest(request);
+ mDeviceStateManagerGlobal.cancelStateRequest();
verify(callback).onStateChanged(eq(mService.getBaseState()));
}
@@ -172,7 +171,7 @@
verify(callback).onRequestActivated(eq(request));
Mockito.reset(callback);
- mDeviceStateManagerGlobal.cancelRequest(request);
+ mDeviceStateManagerGlobal.cancelStateRequest();
verify(callback).onRequestCanceled(eq(request));
}
@@ -203,13 +202,13 @@
private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
private int mBaseState = DEFAULT_DEVICE_STATE;
- private ArrayList<Request> mRequests = new ArrayList<>();
+ private Request mRequest;
private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
private DeviceStateInfo getInfo() {
- final int mergedState = mRequests.isEmpty()
- ? mBaseState : mRequests.get(mRequests.size() - 1).state;
+ final int mergedState = mRequest == null
+ ? mBaseState : mRequest.state;
return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
}
@@ -245,11 +244,10 @@
@Override
public void requestState(IBinder token, int state, int flags) {
- if (!mRequests.isEmpty()) {
- final Request topRequest = mRequests.get(mRequests.size() - 1);
+ if (mRequest != null) {
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
- callback.onRequestSuspended(topRequest.token);
+ callback.onRequestCanceled(mRequest.token);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
@@ -257,7 +255,7 @@
}
final Request request = new Request(token, state, flags);
- mRequests.add(request);
+ mRequest = request;
notifyDeviceStateInfoChanged();
for (IDeviceStateManagerCallback callback : mCallbacks) {
@@ -270,20 +268,9 @@
}
@Override
- public void cancelRequest(IBinder token) {
- int index = -1;
- for (int i = 0; i < mRequests.size(); i++) {
- if (mRequests.get(i).token.equals(token)) {
- index = i;
- break;
- }
- }
-
- if (index == -1) {
- throw new IllegalArgumentException("Unknown request: " + token);
- }
-
- mRequests.remove(index);
+ public void cancelStateRequest() {
+ IBinder token = mRequest.token;
+ mRequest = null;
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onRequestCanceled(token);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index f04a9f7..e16a2f8 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -288,9 +288,8 @@
}
@Override
- public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
- final int deviceType) {
- }
+ public void addVendorCommandListener(
+ final IHdmiVendorCommandListener listener, final int vendorId) {}
@Override
public void sendVendorCommand(final int deviceType, final int targetAddress,
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/com.android.settings.xml b/data/etc/com.android.settings.xml
index ddcab6e..5dcc599 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -61,5 +61,6 @@
<permission name="android.permission.READ_DREAM_SUPPRESSION"/>
<permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index d95644a..ae350ec 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -74,5 +74,6 @@
<permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<permission name="android.permission.FORCE_STOP_PACKAGES" />
<permission name="android.permission.ACCESS_FPS_COUNTER" />
+ <permission name="android.permission.CHANGE_CONFIGURATION" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0e06fac..a331b6e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -334,6 +334,7 @@
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_GAME_MODE"/>
+ <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
<permission name="android.permission.MANAGE_ROLLBACKS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
@@ -391,6 +392,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 +437,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 +474,8 @@
<!-- 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.NEARBY_WIFI_DEVICES" />
+ <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 -->
@@ -577,6 +582,7 @@
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.bips">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 1567233..f2a875c7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -727,12 +727,6 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1343787701": {
- "message": "startBackNavigation task=%s, topRunningActivity=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"-1340540100": {
"message": "Creating SnapshotStartingData",
"level": "VERBOSE",
@@ -1765,6 +1759,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "-228813488": {
+ "message": "%s: Setting back callback %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"-208825711": {
"message": "shouldWaitAnimatingExit: isWallpaperTarget: %s",
"level": "DEBUG",
@@ -3691,6 +3691,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1898905572": {
+ "message": "startBackNavigation task=%s, topRunningActivity=%s, topWindow=%s backCallback=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"1903353011": {
"message": "notifyAppStopped: %s",
"level": "VERBOSE",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 4ae0fc4..5a3a033 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -272,13 +272,13 @@
<!-- fallback fonts -->
<family lang="und-Arab" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ <font weight="400" style="normal">
NotoNaskhArabic-Regular.ttf
</font>
<font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
</family>
<family lang="und-Arab" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ <font weight="400" style="normal">
NotoNaskhArabicUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
@@ -329,7 +329,7 @@
<font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifThai">
+ <font weight="400" style="normal" fallbackFor="serif">
NotoSerifThai-Regular.ttf
</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
@@ -923,16 +923,16 @@
<font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="elegant">
- <font weight="400" style="normal" postScriptName="NotoSansLao">NotoSansLao-Regular.ttf
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifLao">
+ <font weight="400" style="normal" fallbackFor="serif">
NotoSerifLao-Regular.ttf
</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
@@ -1013,7 +1013,7 @@
</font>
</family>
<family lang="und-Cans">
- <font weight="400" style="normal" postScriptName="NotoSansCanadianAboriginal">
+ <font weight="400" style="normal">
NotoSansCanadianAboriginal-Regular.ttf
</font>
</family>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 055e5ad..857af11 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -458,7 +458,7 @@
* No color information is stored.
* With this configuration, each pixel requires 1 byte of memory.
*/
- ALPHA_8 (1),
+ ALPHA_8(1),
/**
* Each pixel is stored on 2 bytes and only the RGB channels are
@@ -479,7 +479,7 @@
* short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
* </pre>
*/
- RGB_565 (3),
+ RGB_565(3),
/**
* Each pixel is stored on 2 bytes. The three RGB color channels
@@ -501,7 +501,7 @@
* it is advised to use {@link #ARGB_8888} instead.
*/
@Deprecated
- ARGB_4444 (4),
+ ARGB_4444(4),
/**
* Each pixel is stored on 4 bytes. Each channel (RGB and alpha
@@ -516,10 +516,10 @@
* int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
* </pre>
*/
- ARGB_8888 (5),
+ ARGB_8888(5),
/**
- * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
+ * Each pixel is stored on 8 bytes. Each channel (RGB and alpha
* for translucency) is stored as a
* {@link android.util.Half half-precision floating point value}.
*
@@ -531,7 +531,7 @@
* long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
* </pre>
*/
- RGBA_F16 (6),
+ RGBA_F16(6),
/**
* Special configuration, when bitmap is stored only in graphic memory.
@@ -540,13 +540,29 @@
* It is optimal for cases, when the only operation with the bitmap is to draw it on a
* screen.
*/
- HARDWARE (7);
+ HARDWARE(7),
+
+ /**
+ * Each pixel is stored on 4 bytes. Each RGB channel is stored with 10 bits of precision
+ * (1024 possible values). There is an additional alpha channel that is stored with 2 bits
+ * of precision (4 possible values).
+ *
+ * This configuration is suited for wide-gamut and HDR content which does not require alpha
+ * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color
+ * precision.
+ *
+ * <p>Use this formula to pack into 32 bits:</p>
+ * <pre class="prettyprint">
+ * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff);
+ * </pre>
+ */
+ RGBA_1010102(8);
@UnsupportedAppUsage
final int nativeInt;
private static Config sConfigs[] = {
- null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
+ null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
};
Config(int ni) {
@@ -1000,8 +1016,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
*
* @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1019,8 +1035,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
* @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
* and {@link ColorSpace.Named#SRGB sRGB} or
@@ -1050,8 +1066,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
*
* @throws IllegalArgumentException if the width or height are <= 0, or if
@@ -1074,8 +1090,8 @@
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
- * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
- * mark the bitmap as opaque. Doing so will clear the bitmap in black
+ * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be
+ * used to mark the bitmap as opaque. Doing so will clear the bitmap in black
* instead of transparent.
* @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}
* and {@link ColorSpace.Named#SRGB sRGB} or
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 3732285..1629b6a 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -368,7 +368,7 @@
* Further, unlike other Sources, this one is not reusable.
*/
private static class InputStreamSource extends Source {
- InputStreamSource(Resources res, InputStream is, int inputDensity) {
+ InputStreamSource(Resources res, @NonNull InputStream is, int inputDensity) {
if (is == null) {
throw new IllegalArgumentException("The InputStream cannot be null");
}
@@ -1020,7 +1020,7 @@
*/
@AnyThread
@NonNull
- public static Source createSource(Resources res, InputStream is) {
+ public static Source createSource(Resources res, @NonNull InputStream is) {
return new InputStreamSource(res, is, Bitmap.getDefaultDensity());
}
@@ -1034,7 +1034,7 @@
@AnyThread
@TestApi
@NonNull
- public static Source createSource(Resources res, InputStream is, int density) {
+ public static Source createSource(Resources res, @NonNull InputStream is, int density) {
return new InputStreamSource(res, is, density);
}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 28b3b04..4972e92 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -288,8 +288,7 @@
*
* @return A copy of the drawable's bounds
*/
- @NonNull
- public final Rect copyBounds() {
+ public final @NonNull Rect copyBounds() {
return new Rect(mBounds);
}
@@ -308,8 +307,7 @@
* @see #copyBounds()
* @see #copyBounds(android.graphics.Rect)
*/
- @NonNull
- public final Rect getBounds() {
+ public final @NonNull Rect getBounds() {
if (mBounds == ZERO_BOUNDS_RECT) {
mBounds = new Rect();
}
@@ -327,8 +325,7 @@
*
* @return The dirty bounds of this drawable
*/
- @NonNull
- public Rect getDirtyBounds() {
+ public @NonNull Rect getDirtyBounds() {
return getBounds();
}
@@ -457,8 +454,7 @@
*
* @see #setCallback(android.graphics.drawable.Drawable.Callback)
*/
- @Nullable
- public Callback getCallback() {
+ public @Nullable Callback getCallback() {
return mCallback != null ? mCallback.get() : null;
}
@@ -569,8 +565,7 @@
* The default return value is 255 if the class does not override this method to return a value
* specific to its use of alpha.
*/
- @IntRange(from=0,to=255)
- public int getAlpha() {
+ public @IntRange(from=0,to=255) int getAlpha() {
return 0xFF;
}
@@ -999,7 +994,8 @@
*
* @see android.graphics.PixelFormat
*/
- @Deprecated public abstract @PixelFormat.Opacity int getOpacity();
+ @Deprecated
+ public abstract @PixelFormat.Opacity int getOpacity();
/**
* Return the appropriate opacity value for two source opacities. If
@@ -1059,7 +1055,7 @@
* if it looks the same and there is no need to redraw it since its
* last state.
*/
- protected boolean onStateChange(int[] state) {
+ protected boolean onStateChange(@NonNull int[] state) {
return false;
}
@@ -1078,7 +1074,7 @@
* Override this in your subclass to change appearance if you vary based on
* the bounds.
*/
- protected void onBoundsChange(Rect bounds) {
+ protected void onBoundsChange(@NonNull Rect bounds) {
// Stub method.
}
@@ -1209,7 +1205,8 @@
/**
* Create a drawable from an inputstream
*/
- public static Drawable createFromStream(InputStream is, String srcName) {
+ public static @Nullable Drawable createFromStream(@Nullable InputStream is,
+ @Nullable String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(null, null, is, srcName);
@@ -1222,8 +1219,8 @@
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
*/
- public static Drawable createFromResourceStream(Resources res, TypedValue value,
- InputStream is, String srcName) {
+ public static @Nullable Drawable createFromResourceStream(@Nullable Resources res,
+ @Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable");
try {
return createFromResourceStream(res, value, is, srcName, null);
@@ -1238,8 +1235,7 @@
*
* @deprecated Prefer the version without an Options object.
*/
- @Nullable
- public static Drawable createFromResourceStream(@Nullable Resources res,
+ public static @Nullable Drawable createFromResourceStream(@Nullable Resources res,
@Nullable TypedValue value, @Nullable InputStream is, @Nullable String srcName,
@Nullable BitmapFactory.Options opts) {
if (is == null) {
@@ -1281,7 +1277,8 @@
return null;
}
- private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
+ private static Drawable getBitmapDrawable(Resources res, @Nullable TypedValue value,
+ @NonNull InputStream is) {
try {
ImageDecoder.Source source = null;
if (value != null) {
@@ -1369,9 +1366,9 @@
* a tag in an XML document, tries to create a Drawable from that tag.
* Returns null if the tag is not a valid drawable.
*/
- @NonNull
- public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
- @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
+ public static @NonNull Drawable createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs)
+ throws XmlPullParserException, IOException {
return createFromXmlInner(r, parser, attrs, null);
}
@@ -1381,9 +1378,8 @@
* document, tries to create a Drawable from that tag. Returns {@code null}
* if the tag is not a valid drawable.
*/
- @NonNull
- public static Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
- @NonNull AttributeSet attrs, @Nullable Theme theme)
+ public static @NonNull Drawable createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
return createFromXmlInnerForDensity(r, parser, attrs, 0, theme);
}
@@ -1392,8 +1388,7 @@
* Version of {@link #createFromXmlInner(Resources, XmlPullParser, AttributeSet, Theme)} that
* accepts an override density.
*/
- @NonNull
- static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
+ static @NonNull Drawable createFromXmlInnerForDensity(@NonNull Resources r,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
@Nullable Theme theme) throws XmlPullParserException, IOException {
return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
@@ -1403,8 +1398,7 @@
/**
* Create a drawable from file path name.
*/
- @Nullable
- public static Drawable createFromPath(String pathName) {
+ public static @Nullable Drawable createFromPath(String pathName) {
if (pathName == null) {
return null;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 66752a2..8debe26 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -61,8 +61,7 @@
* @param id the identifier of the drawable resource
* @return a drawable, or {@code null} if the drawable failed to load
*/
- @Nullable
- public static Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
+ public static @Nullable Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
return loadDrawable(context.getResources(), context.getTheme(), id);
}
@@ -74,9 +73,8 @@
* @param id the identifier of the drawable resource
* @return a drawable, or {@code null} if the drawable failed to load
*/
- @Nullable
- public static Drawable loadDrawable(
- @NonNull Resources resources, @Nullable Theme theme, @DrawableRes int id) {
+ public static @Nullable Drawable loadDrawable(@NonNull Resources resources,
+ @Nullable Theme theme, @DrawableRes int id) {
return resources.getDrawable(id, theme);
}
@@ -111,8 +109,7 @@
* @throws XmlPullParserException
* @throws IOException
*/
- @NonNull
- public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
+ public @NonNull Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
return inflateFromXmlForDensity(name, parser, attrs, 0, theme);
@@ -122,8 +119,7 @@
* Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
* an override density.
*/
- @NonNull
- Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
+ @NonNull Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
// Inner classes must be referenced as Outer$Inner, but XML tag names
@@ -146,9 +142,8 @@
return drawable;
}
- @NonNull
@SuppressWarnings("deprecation")
- private Drawable inflateFromTag(@NonNull String name) {
+ private @Nullable Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
@@ -195,8 +190,7 @@
}
}
- @NonNull
- private Drawable inflateFromClass(@NonNull String className) {
+ private @NonNull Drawable inflateFromClass(@NonNull String className) {
try {
Constructor<? extends Drawable> constructor;
synchronized (CONSTRUCTOR_MAP) {
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index ebde757..a63d7f6 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -352,7 +352,7 @@
}
@Override
- protected boolean onStateChange(int[] state) {
+ protected boolean onStateChange(@NonNull int[] state) {
if (mDrawable != null && mDrawable.isStateful()) {
final boolean changed = mDrawable.setState(state);
if (changed) {
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index a03931b..b04b826 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -302,7 +302,7 @@
* is available. The {@link android.os.Message#obj obj}
* property is populated with the Drawable.
*/
- public void loadDrawableAsync(Context context, Message andThen) {
+ public void loadDrawableAsync(@NonNull Context context, @NonNull Message andThen) {
if (andThen.getTarget() == null) {
throw new IllegalArgumentException("callback message must have a target handler");
}
@@ -320,7 +320,7 @@
* {@link #loadDrawable(Context)} finished
* @param handler {@link Handler} on which to notify the {@code listener}
*/
- public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener,
+ public void loadDrawableAsync(@NonNull Context context, final OnDrawableLoadedListener listener,
Handler handler) {
new LoadDrawableTask(context, handler, listener).runAsync();
}
@@ -335,7 +335,7 @@
* to access {@link android.content.res.Resources Resources}, for example.
* @return A fresh instance of a drawable for this image, yours to keep.
*/
- public Drawable loadDrawable(Context context) {
+ public @Nullable Drawable loadDrawable(Context context) {
final Drawable result = loadDrawableInner(context);
if (result != null && hasTint()) {
result.mutate();
@@ -415,7 +415,7 @@
return null;
}
- private InputStream getUriInputStream(Context context) {
+ private @Nullable InputStream getUriInputStream(Context context) {
final Uri uri = getUri();
final String scheme = uri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equals(scheme)
@@ -496,7 +496,7 @@
* @param stream The stream on which to serialize the Icon.
* @hide
*/
- public void writeToStream(OutputStream stream) throws IOException {
+ public void writeToStream(@NonNull OutputStream stream) throws IOException {
DataOutputStream dataStream = new DataOutputStream(stream);
dataStream.writeInt(VERSION_STREAM_SERIALIZER);
@@ -532,7 +532,7 @@
* @param stream The input stream from which to reconstruct the Icon.
* @hide
*/
- public static Icon createFromStream(InputStream stream) throws IOException {
+ public static @Nullable Icon createFromStream(@NonNull InputStream stream) throws IOException {
DataInputStream inputStream = new DataInputStream(stream);
final int version = inputStream.readInt();
@@ -571,7 +571,7 @@
* @return whether this icon is the same as the another one
* @hide
*/
- public boolean sameAs(Icon otherIcon) {
+ public boolean sameAs(@NonNull Icon otherIcon) {
if (otherIcon == this) {
return true;
}
@@ -602,7 +602,7 @@
* given resource ID.
* @param resId ID of the drawable resource
*/
- public static Icon createWithResource(Context context, @DrawableRes int resId) {
+ public static @NonNull Icon createWithResource(Context context, @DrawableRes int resId) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
@@ -617,7 +617,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static Icon createWithResource(Resources res, @DrawableRes int resId) {
+ public static @NonNull Icon createWithResource(Resources res, @DrawableRes int resId) {
if (res == null) {
throw new IllegalArgumentException("Resource must not be null.");
}
@@ -632,7 +632,7 @@
* @param resPackage Name of the package containing the resource in question
* @param resId ID of the drawable resource
*/
- public static Icon createWithResource(String resPackage, @DrawableRes int resId) {
+ public static @NonNull Icon createWithResource(String resPackage, @DrawableRes int resId) {
if (resPackage == null) {
throw new IllegalArgumentException("Resource package name must not be null.");
}
@@ -646,7 +646,7 @@
* Create an Icon pointing to a bitmap in memory.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
- public static Icon createWithBitmap(Bitmap bits) {
+ public static @NonNull Icon createWithBitmap(Bitmap bits) {
if (bits == null) {
throw new IllegalArgumentException("Bitmap must not be null.");
}
@@ -660,7 +660,7 @@
* by {@link AdaptiveIconDrawable}.
* @param bits A valid {@link android.graphics.Bitmap} object
*/
- public static Icon createWithAdaptiveBitmap(Bitmap bits) {
+ public static @NonNull Icon createWithAdaptiveBitmap(Bitmap bits) {
if (bits == null) {
throw new IllegalArgumentException("Bitmap must not be null.");
}
@@ -677,7 +677,7 @@
* @param offset Offset into <code>data</code> at which the bitmap data starts
* @param length Length of the bitmap data
*/
- public static Icon createWithData(byte[] data, int offset, int length) {
+ public static @NonNull Icon createWithData(byte[] data, int offset, int length) {
if (data == null) {
throw new IllegalArgumentException("Data must not be null.");
}
@@ -693,7 +693,7 @@
*
* @param uri A uri referring to local content:// or file:// image data.
*/
- public static Icon createWithContentUri(String uri) {
+ public static @NonNull Icon createWithContentUri(String uri) {
if (uri == null) {
throw new IllegalArgumentException("Uri must not be null.");
}
@@ -707,7 +707,7 @@
*
* @param uri A uri referring to local content:// or file:// image data.
*/
- public static Icon createWithContentUri(Uri uri) {
+ public static @NonNull Icon createWithContentUri(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("Uri must not be null.");
}
@@ -720,8 +720,7 @@
*
* @param uri A uri referring to local content:// or file:// image data.
*/
- @NonNull
- public static Icon createWithAdaptiveBitmapContentUri(@NonNull String uri) {
+ public static @NonNull Icon createWithAdaptiveBitmapContentUri(@NonNull String uri) {
if (uri == null) {
throw new IllegalArgumentException("Uri must not be null.");
}
@@ -750,7 +749,7 @@
* @param tint a color, as in {@link Drawable#setTint(int)}
* @return this same object, for use in chained construction
*/
- public Icon setTint(@ColorInt int tint) {
+ public @NonNull Icon setTint(@ColorInt int tint) {
return setTintList(ColorStateList.valueOf(tint));
}
@@ -760,7 +759,7 @@
* @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint
* @return this same object, for use in chained construction
*/
- public Icon setTintList(ColorStateList tintList) {
+ public @NonNull Icon setTintList(ColorStateList tintList) {
mTintList = tintList;
return this;
}
@@ -809,7 +808,7 @@
* @param path A path to a file that contains compressed bitmap data of
* a type that {@link android.graphics.BitmapFactory} can decode.
*/
- public static Icon createWithFilePath(String path) {
+ public static @NonNull Icon createWithFilePath(String path) {
if (path == null) {
throw new IllegalArgumentException("Path must not be null.");
}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index cdf746f..f440b69 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -454,7 +454,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* generated CBOR and enables the issuing authority to verify that the
- * returned proof is fresh.
+ * returned proof is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the COSE_Sign1 data structure above
*/
public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
@@ -485,7 +486,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* generated CBOR and enables the issuing authority to verify that the
- * returned proof is fresh.
+ * returned proof is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the COSE_Sign1 data structure above
*/
public @NonNull byte[] delete(@NonNull byte[] challenge) {
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
index 305d0ea..6d56964 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -59,7 +59,8 @@
* @param challenge is a non-empty byte array whose contents should be unique, fresh and
* provided by the issuing authority. The value provided is embedded in the
* attestation extension and enables the issuing authority to verify that the
- * attestation certificate is fresh.
+ * attestation certificate is fresh. Implementations are required to support
+ * challenges at least 32 bytes of length.
* @return the X.509 certificate for this credential's CredentialKey.
*/
public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
deleted file mode 100644
index a309d48..0000000
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.wm.shell.compatui.LetterboxEduToastLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@color/compat_controls_background"
- android:gravity="center"
- android:paddingVertical="14dp"
- android:paddingHorizontal="16dp">
-
- <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
- <LinearLayout
- android:id="@+id/letterbox_education_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/letterbox_education_icon"
- android:layout_width="@dimen/letterbox_education_toast_icon_size"
- android:layout_height="@dimen/letterbox_education_toast_icon_size"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxWidth="@dimen/letterbox_education_toast_text_max_width"
- android:paddingHorizontal="16dp"
- android:lineSpacingExtra="5sp"
- android:text="@string/letterbox_education_toast_title"
- android:textAlignment="viewStart"
- android:textColor="@color/compat_controls_text"
- android:textSize="16sp"
- android:maxLines="1"
- android:ellipsize="end"/>
-
- <ImageButton
- android:id="@+id/letterbox_education_toast_expand"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/letterbox_education_ic_expand_more_ripple"
- android:background="@android:color/transparent"
- android:contentDescription="@string/letterbox_education_expand_button_description"/>
-
- </LinearLayout>
-
-</com.android.wm.shell.compatui.LetterboxEduToastLayout>
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 790a6d4..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.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 84cf285..76b4036 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -36,16 +36,16 @@
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
- <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string>
- <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string>
- <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్"</string>
+ <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
- <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string>
+ <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string>
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/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index ab2c9b1..40c7647 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -219,18 +219,9 @@
<!-- The width of the camera compat hint. -->
<dimen name="camera_compat_hint_width">143dp</dimen>
- <!-- The corner radius of the letterbox education toast. -->
- <dimen name="letterbox_education_toast_corner_radius">100dp</dimen>
-
<!-- The corner radius of the letterbox education dialog. -->
<dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
- <!-- The margin between the letterbox education toast/dialog and the bottom of the task. -->
- <dimen name="letterbox_education_margin_bottom">16dp</dimen>
-
- <!-- The size of the icon in the letterbox education toast. -->
- <dimen name="letterbox_education_toast_icon_size">24dp</dimen>
-
<!-- The size of an icon in the letterbox education dialog. -->
<dimen name="letterbox_education_dialog_icon_size">48dp</dimen>
@@ -243,9 +234,6 @@
<!-- The maximum width of the title and subtitle in the letterbox education dialog. -->
<dimen name="letterbox_education_dialog_title_max_width">444dp</dimen>
- <!-- The maximum width of the text in the letterbox education toast. -->
- <dimen name="letterbox_education_toast_text_max_width">398dp</dimen>
-
<!-- The distance that the letterbox education dialog will move up during appear/dismiss
animation. -->
<dimen name="letterbox_education_dialog_animation_elevation">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a8a9ed7..16a4b52 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -174,9 +174,6 @@
<!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
<string name="letterbox_education_dialog_title">Get the most out of <xliff:g id="app_name" example="YouTube">%s</xliff:g></string>
- <!-- The title of the letterbox education toast. [CHAR LIMIT=60] -->
- <string name="letterbox_education_toast_title">Rotate your device for a full-screen view</string>
-
<!-- Description of the rotate screen into portrait action. [CHAR LIMIT=NONE] -->
<string name="letterbox_education_screen_rotation_portrait_text">Rotate your screen to portrait</string>
@@ -192,7 +189,4 @@
<!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
<string name="letterbox_education_got_it">Got it</string>
- <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
- <string name="letterbox_education_expand_button_description">Expand for more information.</string>
-
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a477bd7..7ab6835 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1790,6 +1790,7 @@
/**
* Changes the expanded state of the stack.
+ * Don't call this directly, call mBubbleData#setExpanded.
*
* @param shouldExpand whether the bubble stack should appear expanded
*/
@@ -1836,7 +1837,7 @@
} else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
mManageEduView.hide();
} else {
- setExpanded(false);
+ mBubbleData.setExpanded(false);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e2bc360..9384e2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -245,7 +245,8 @@
}
}
- private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+ private void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {
synchronized (mDisplays) {
if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
@@ -253,7 +254,8 @@
return;
}
for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
- mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas);
+ mDisplayChangedListeners.get(i)
+ .onKeepClearAreasChanged(displayId, restricted, unrestricted);
}
}
}
@@ -318,9 +320,10 @@
}
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {
mMainExecutor.execute(() -> {
- DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas);
+ DisplayController.this.onKeepClearAreasChanged(displayId, restricted, unrestricted);
});
}
}
@@ -361,6 +364,7 @@
/**
* Called when keep-clear areas on a display have changed.
*/
- default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+ default void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
deleted file mode 100644
index e7f592d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.compatui.letterboxedu;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.wm.shell.R;
-
-/**
- * Container for the Letterbox Education Toast.
- */
-// TODO(b/215316431): Add tests
-public class LetterboxEduToastLayout extends FrameLayout {
-
- public LetterboxEduToastLayout(Context context) {
- this(context, null);
- }
-
- public LetterboxEduToastLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- /**
- * Register a callback for the dismiss button.
- * @param callback The callback to register
- */
- void setExpandOnClickListener(Runnable callback) {
- findViewById(R.id.letterbox_education_toast_expand).setOnClickListener(
- view -> callback.run());
- }
-
- /**
- * Updates the layout with the given app info.
- * @param appName The name of the app
- * @param appIcon The icon of the app
- */
- void updateAppInfo(String appName, Drawable appIcon) {
- ImageView icon = findViewById(R.id.letterbox_education_icon);
- icon.setContentDescription(appName);
- icon.setImageDrawable(appIcon);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f61e624..9f4ff7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -471,9 +471,10 @@
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
DisplayController displayController, Context context,
@ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
return new Transitions(organizer, pool, displayController, context, mainExecutor,
- animExecutor);
+ mainHandler, animExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index eda09e3..5ebdceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -150,43 +150,45 @@
if (inLandscape) {
final Rect leftHitRegion = new Rect();
- final Rect leftDrawRegion = topOrLeftBounds;
final Rect rightHitRegion = new Rect();
- final Rect rightDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- // Add the divider bounds to each side since that counts for the hit region.
- leftHitRegion.set(topOrLeftBounds);
- leftHitRegion.right += dividerWidth / 2;
- rightHitRegion.set(bottomOrRightBounds);
- rightHitRegion.left -= dividerWidth / 2;
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.right + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ leftHitRegion.set(displayRegion);
+ leftHitRegion.right = (int) centerX;
+ rightHitRegion.set(displayRegion);
+ rightHitRegion.left = (int) centerX;
} else {
displayRegion.splitVertically(leftHitRegion, rightHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds));
} else {
final Rect topHitRegion = new Rect();
- final Rect topDrawRegion = topOrLeftBounds;
final Rect bottomHitRegion = new Rect();
- final Rect bottomDrawRegion = bottomOrRightBounds;
// If we have existing split regions use those bounds, otherwise split it 50/50
if (inSplitScreen) {
- // Add the divider bounds to each side since that counts for the hit region.
- topHitRegion.set(topOrLeftBounds);
- topHitRegion.bottom += dividerWidth / 2;
- bottomHitRegion.set(bottomOrRightBounds);
- bottomHitRegion.top -= dividerWidth / 2;
+ // The bounds of the existing split will have a divider bar, the hit region
+ // should include that space. Find the center of the divider bar:
+ float centerX = topOrLeftBounds.bottom + (dividerWidth / 2);
+ // Now set the hit regions using that center.
+ topHitRegion.set(displayRegion);
+ topHitRegion.bottom = (int) centerX;
+ bottomHitRegion.set(displayRegion);
+ bottomHitRegion.top = (int) centerX;
} else {
displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
- mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
+ mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds));
+ mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds));
}
} else {
// Split-screen not allowed, so only show the fullscreen target
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index fd3be2b..7307ba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -24,7 +24,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -103,7 +102,7 @@
MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
- updateContainerMargins();
+ updateContainerMargins(getResources().getConfiguration().orientation);
}
@Override
@@ -128,20 +127,18 @@
}
public void onConfigChanged(Configuration newConfig) {
- final int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
&& getOrientation() != HORIZONTAL) {
setOrientation(LinearLayout.HORIZONTAL);
- updateContainerMargins();
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT
+ updateContainerMargins(newConfig.orientation);
+ } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
&& getOrientation() != VERTICAL) {
setOrientation(LinearLayout.VERTICAL);
- updateContainerMargins();
+ updateContainerMargins(newConfig.orientation);
}
}
- private void updateContainerMargins() {
- final int orientation = getResources().getConfiguration().orientation;
+ private void updateContainerMargins(int orientation) {
final float halfMargin = mDisplayMargin / 2f;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mDropZoneView1.setContainerMargin(
@@ -156,10 +153,6 @@
}
}
- public boolean hasDropTarget() {
- return mCurrentTarget != null;
- }
-
public boolean hasDropped() {
return mHasDropped;
}
@@ -271,6 +264,9 @@
* Updates the visible drop target as the user drags.
*/
public void update(DragEvent event) {
+ if (mHasDropped) {
+ return;
+ }
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
@@ -286,7 +282,8 @@
animateHighlight(target);
} else {
// Switching between targets
- animateHighlight(target);
+ mDropZoneView1.animateSwitch();
+ mDropZoneView2.animateSwitch();
}
mCurrentTarget = target;
}
@@ -323,7 +320,7 @@
: DISABLE_NONE);
mDropZoneView1.setShowingMargin(visible);
mDropZoneView2.setShowingMargin(visible);
- ObjectAnimator animator = mDropZoneView1.getAnimator();
+ Animator animator = mDropZoneView1.getAnimator();
if (animCompleteCallback != null) {
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@@ -343,17 +340,11 @@
if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
- mDropZoneView1.setShowingSplash(false);
-
mDropZoneView2.setShowingHighlight(false);
- mDropZoneView2.setShowingSplash(true);
} else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
|| target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
mDropZoneView1.setShowingHighlight(false);
- mDropZoneView1.setShowingSplash(true);
-
mDropZoneView2.setShowingHighlight(true);
- mDropZoneView2.setShowingSplash(false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 2f47af5..a3ee8ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -18,6 +18,7 @@
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
+import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -27,7 +28,6 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -43,8 +43,8 @@
*/
public class DropZoneView extends FrameLayout {
- private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
- private static final int HIGHLIGHT_ALPHA_INT = 255;
+ private static final float SPLASHSCREEN_ALPHA = 0.90f;
+ private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
@@ -61,54 +61,27 @@
}
};
- private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
- new IntProperty<ColorDrawable>("splashscreen") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
- private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
- new IntProperty<ColorDrawable>("highlight") {
- @Override
- public void setValue(ColorDrawable d, int alpha) {
- d.setAlpha(alpha);
- }
-
- @Override
- public Integer get(ColorDrawable d) {
- return d.getAlpha();
- }
- };
-
private final Path mPath = new Path();
private final float[] mContainerMargin = new float[4];
private float mCornerRadius;
private float mBottomInset;
private int mMarginColor; // i.e. color used for negative space like the container insets
- private int mHighlightColor;
private boolean mShowingHighlight;
private boolean mShowingSplash;
private boolean mShowingMargin;
- // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
- private ObjectAnimator mSplashAnimator;
- private ObjectAnimator mHighlightAnimator;
+ private int mSplashScreenColor;
+ private int mHighlightColor;
+
+ private ObjectAnimator mBackgroundAnimator;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
// Renders a highlight or neutral transparent color
- private ColorDrawable mDropZoneDrawable;
+ private ColorDrawable mColorDrawable;
// Renders the translucent splashscreen with the app icon in the middle
private ImageView mSplashScreenView;
- private ColorDrawable mSplashBackgroundDrawable;
// Renders the margin / insets around the dropzone container
private MarginView mMarginView;
@@ -130,19 +103,14 @@
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mMarginColor = getResources().getColor(R.color.taskbar_background);
- mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
-
- mDropZoneDrawable = new ColorDrawable();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(0);
- setBackgroundDrawable(mDropZoneDrawable);
+ int c = getResources().getColor(android.R.color.system_accent1_500);
+ mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
+ mColorDrawable = new ColorDrawable();
+ setBackgroundDrawable(mColorDrawable);
mSplashScreenView = new ImageView(context);
mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
- mSplashBackgroundDrawable = new ColorDrawable();
- mSplashBackgroundDrawable.setColor(Color.WHITE);
- mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
- mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mSplashScreenView.setAlpha(0f);
@@ -157,10 +125,6 @@
mMarginColor = getResources().getColor(R.color.taskbar_background);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
- final int alpha = mDropZoneDrawable.getAlpha();
- mDropZoneDrawable.setColor(mHighlightColor);
- mDropZoneDrawable.setAlpha(alpha);
-
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
@@ -187,38 +151,39 @@
}
/** Sets the color and icon to use for the splashscreen when shown. */
- public void setAppInfo(int splashScreenColor, Drawable appIcon) {
- mSplashBackgroundDrawable.setColor(splashScreenColor);
+ public void setAppInfo(int color, Drawable appIcon) {
+ Color c = Color.valueOf(color);
+ mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue());
mSplashScreenView.setImageDrawable(appIcon);
}
/** @return an active animator for this view if one exists. */
@Nullable
- public ObjectAnimator getAnimator() {
+ public Animator getAnimator() {
if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
return mMarginAnimator;
- } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
- return mHighlightAnimator;
- } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
- return mSplashAnimator;
+ } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) {
+ return mBackgroundAnimator;
}
return null;
}
- /** Animates the splashscreen to show or hide. */
- public void setShowingSplash(boolean showingSplash) {
- if (mShowingSplash != showingSplash) {
- mShowingSplash = showingSplash;
- animateSplashToState();
- }
+ /** Animates between highlight and splashscreen depending on current state. */
+ public void animateSwitch() {
+ mShowingHighlight = !mShowingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(mColorDrawable.getColor(), newColor);
+ animateSplashScreenIcon();
}
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
- if (mShowingHighlight != showingHighlight) {
- mShowingHighlight = showingHighlight;
- animateHighlightToState();
- }
+ mShowingHighlight = showingHighlight;
+ mShowingSplash = !mShowingHighlight;
+ final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
+ animateBackground(Color.TRANSPARENT, newColor);
+ animateSplashScreenIcon();
}
/** Animates the margins around the drop zone to show or hide. */
@@ -228,40 +193,31 @@
animateMarginToState();
}
if (!mShowingMargin) {
- setShowingHighlight(false);
- setShowingSplash(false);
+ mShowingHighlight = false;
+ mShowingSplash = false;
+ animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT);
+ animateSplashScreenIcon();
}
}
- private void animateSplashToState() {
- if (mSplashAnimator != null) {
- mSplashAnimator.cancel();
+ private void animateBackground(int startColor, int endColor) {
+ if (mBackgroundAnimator != null) {
+ mBackgroundAnimator.cancel();
}
- mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
- SPLASHSCREEN_ALPHA,
- mSplashBackgroundDrawable.getAlpha(),
- mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
- if (!mShowingSplash) {
- mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable,
+ "color",
+ startColor,
+ endColor);
+ if (!mShowingSplash && !mShowingHighlight) {
+ mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
- mSplashAnimator.start();
+ mBackgroundAnimator.start();
+ }
+
+ private void animateSplashScreenIcon() {
mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
}
- private void animateHighlightToState() {
- if (mHighlightAnimator != null) {
- mHighlightAnimator.cancel();
- }
- mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
- HIGHLIGHT_ALPHA,
- mDropZoneDrawable.getAlpha(),
- mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
- if (!mShowingHighlight) {
- mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- }
- mHighlightAnimator.start();
- }
-
private void animateMarginToState() {
if (mMarginAnimator != null) {
mMarginAnimator.cancel();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 17005ea..6b0d7f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -867,6 +867,10 @@
}
private void fadeExistingPip(boolean show) {
+ if (mLeash == null || !mLeash.isValid()) {
+ Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash);
+ return;
+ }
final float alphaStart = show ? 0 : 1;
final float alphaEnd = show ? 1 : 0;
mPipAnimationController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 72ead00..32861b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -103,7 +103,7 @@
};
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
- mainHandler);
+ mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 3cfa541..d022ec1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -89,9 +89,17 @@
/**
* Version of startTasks using legacy transition system.
*/
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ float splitRatio, in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Start a pair of intent and task using legacy transition system.
+ */
+ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
+ in Intent fillInIntent, int taskId, boolean intentFirst, in Bundle mainOptions,
+ in Bundle sideOptions, int sidePosition, float splitRatio,
+ in RemoteAnimationAdapter adapter) = 12;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
@@ -100,5 +108,7 @@
* @param appTargets apps that will be re-parented to display area
*/
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- in RemoteAnimationTarget[] appTargets) = 12;
+ in RemoteAnimationTarget[] appTargets) = 13;
+
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3e6dc82..990b53a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -641,6 +641,18 @@
}
@Override
+ public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ Intent fillInIntent, int taskId, boolean intentFirst, Bundle mainOptions,
+ Bundle sideOptions, int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController,
+ "startIntentAndTaskWithLegacyTransition", (controller) ->
+ controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
+ pendingIntent, fillInIntent, taskId, intentFirst, mainOptions,
+ sideOptions, sidePosition, splitRatio, adapter));
+ }
+
+ @Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e592101..219530b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -57,8 +57,10 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
@@ -467,6 +469,116 @@
mTaskOrganizer.applyTransaction(wct);
}
+ /** Start an intent and a task ordered by {@code intentFirst}. */
+ void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
+ int taskId, boolean intentFirst, @Nullable Bundle mainOptions,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter) {
+ // TODO: try pulling the first chunk of this method into a method so that it can be shared
+ // with startTasksWithLegacyTransition. So far attempts to do so result in failure in split.
+
+ // Init divider first to make divider leash for remote animation target.
+ mSplitLayout.init();
+ // Set false to avoid record new bounds with old task still on top;
+ mShouldUpdateRecents = false;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
+ prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ mShouldUpdateRecents = true;
+ mSyncQueue.queue(evictWct);
+ mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ try {
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Unable to boost animation thread. This should only happen"
+ + " during unit tests");
+ }
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
+ augmentedNonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ mShouldUpdateRecents = true;
+ mSyncQueue.queue(evictWct);
+ mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ mainOptions = mainActivityOptions.toBundle();
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ mSplitLayout.setDivideRatio(splitRatio);
+ if (mMainStage.isActive()) {
+ mMainStage.moveToTop(getMainStageBounds(), wct);
+ } else {
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
+ }
+ mSideStage.moveToTop(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ if (intentFirst) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, mainOptions);
+ wct.startTask(taskId, sideOptions);
+ } else {
+ wct.startTask(taskId, mainOptions);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ }
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -879,6 +991,7 @@
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ updateUnfoldBounds();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 13e81bd..ddf01a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -25,6 +25,11 @@
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
@@ -57,16 +62,27 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
+import android.os.Handler;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -92,6 +108,8 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@@ -118,6 +136,7 @@
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
+ private final DevicePolicyManager mDevicePolicyManager;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -132,9 +151,24 @@
private ScreenRotationAnimation mRotationAnimation;
+ private Drawable mEnterpriseThumbnailDrawable;
+
+ private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean isDrawable = intent.getBooleanExtra(
+ EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false);
+ if (!isDrawable) {
+ return;
+ }
+ updateEnterpriseThumbnailDrawable();
+ }
+ };
+
DefaultTransitionHandler(@NonNull DisplayController displayController,
@NonNull TransactionPool transactionPool, Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
@@ -143,9 +177,23 @@
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ updateEnterpriseThumbnailDrawable();
+ mContext.registerReceiver(
+ mEnterpriseResourceUpdatedReceiver,
+ new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
+ /* broadcastPermission = */ null,
+ mainHandler);
+
AttributeCache.init(context);
}
+ private void updateEnterpriseThumbnailDrawable() {
+ mEnterpriseThumbnailDrawable = mDevicePolicyManager.getDrawable(
+ WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+ () -> mContext.getDrawable(R.drawable.ic_corp_badge));
+ }
+
@VisibleForTesting
static boolean isRotationSeamless(@NonNull TransitionInfo info,
DisplayController displayController) {
@@ -290,6 +338,9 @@
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
+ final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+ new ArrayList<>();
+
@ColorInt int backgroundColorForTransition = 0;
final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -361,30 +412,61 @@
}
}
- float cornerRadius = 0;
+ final float cornerRadius;
if (a.hasRoundedCorners() && isTask) {
// hasRoundedCorners is currently only enabled for tasks
final Context displayContext =
mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
cornerRadius =
ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
+ } else {
+ cornerRadius = 0;
}
if (a.getShowBackground()) {
- // use the window's background color if provided as the background color for the
- // animation - the top most window with a valid background color and
- // showBackground set takes precedence.
- if (change.getBackgroundColor() != 0) {
+ if (info.getAnimationOptions().getBackgroundColor() != 0) {
+ // If available use the background color provided through AnimationOptions
+ backgroundColorForTransition =
+ info.getAnimationOptions().getBackgroundColor();
+ } else if (change.getBackgroundColor() != 0) {
+ // Otherwise default to the window's background color if provided through
+ // the theme as the background color for the animation - the top most window
+ // with a valid background color and showBackground set takes precedence.
backgroundColorForTransition = change.getBackgroundColor();
}
}
+ boolean delayedEdgeExtension = false;
+ if (!isTask && a.hasExtension()) {
+ if (!Transitions.isOpeningType(change.getMode())) {
+ // Can screenshot now (before startTransaction is applied)
+ edgeExtendWindow(change, a, startTransaction, finishTransaction);
+ } else {
+ // Need to screenshot after startTransaction is applied otherwise activity
+ // may not be visible or ready yet.
+ postStartTransactionCallbacks
+ .add(t -> edgeExtendWindow(change, a, t, finishTransaction));
+ delayedEdgeExtension = true;
+ }
+ }
+
final Rect clipRect = Transitions.isClosingType(change.getMode())
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
- startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
- cornerRadius, clipRect);
+
+ if (delayedEdgeExtension) {
+ // If the edge extension needs to happen after the startTransition has been
+ // applied, then we want to only start the animation after the edge extension
+ // postStartTransaction callback has been run
+ postStartTransactionCallbacks.add(t ->
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor,
+ null /* position */, cornerRadius, clipRect));
+ } else {
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
+ cornerRadius, clipRect);
+ }
if (info.getAnimationOptions() != null) {
attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(),
@@ -398,7 +480,20 @@
startTransaction, finishTransaction);
}
- startTransaction.apply();
+ // postStartTransactionCallbacks require that the start transaction is already
+ // applied to run otherwise they may result in flickers and UI inconsistencies.
+ boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0;
+ startTransaction.apply(waitForStartTransactionApply);
+
+ // Run tasks that require startTransaction to already be applied
+ for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+ postStartTransactionCallbacks) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
+ postStartTransactionCallback.accept(t);
+ t.apply();
+ mTransactionPool.release(t);
+ }
+
mRotator.cleanUp(finishTransaction);
TransitionMetrics.getInstance().reportAnimationStart(transition);
// run finish now in-case there are no animations
@@ -406,6 +501,117 @@
return true;
}
+ private void edgeExtendWindow(TransitionInfo.Change change,
+ Animation a, SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final Transformation transformationAtStart = new Transformation();
+ a.getTransformationAt(0, transformationAtStart);
+ final Transformation transformationAtEnd = new Transformation();
+ a.getTransformationAt(1, transformationAtEnd);
+
+ // We want to create an extension surface that is the maximal size and the animation will
+ // take care of cropping any part that overflows.
+ final Insets maxExtensionInsets = Insets.min(
+ transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+ final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(),
+ change.getEndAbsBounds().height());
+ final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+ change.getEndAbsBounds().width());
+ if (maxExtensionInsets.left < 0) {
+ final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.left, targetSurfaceHeight);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Left Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.top < 0) {
+ final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.top);
+ final int xPos = 0;
+ final int yPos = maxExtensionInsets.top;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Top Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.right < 0) {
+ final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ -maxExtensionInsets.right, targetSurfaceHeight);
+ final int xPos = targetSurfaceWidth;
+ final int yPos = 0;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Right Edge Extension", startTransaction, finishTransaction);
+ }
+
+ if (maxExtensionInsets.bottom < 0) {
+ final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+ targetSurfaceWidth, targetSurfaceHeight);
+ final Rect extensionRect = new Rect(0, 0,
+ targetSurfaceWidth, -maxExtensionInsets.bottom);
+ final int xPos = maxExtensionInsets.left;
+ final int yPos = targetSurfaceHeight;
+ createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+ "Bottom Edge Extension", startTransaction, finishTransaction);
+ }
+ }
+
+ private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
+ Rect extensionRect, int xPos, int yPos, String layerName,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+ .setName(layerName)
+ .setParent(surfaceToExtend)
+ .setHidden(true)
+ .setCallsite("DefaultTransitionHandler#startAnimation")
+ .setOpaque(true)
+ .setBufferSize(extensionRect.width(), extensionRect.height())
+ .build();
+
+ SurfaceControl.LayerCaptureArgs captureArgs =
+ new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+ .setSourceCrop(edgeBounds)
+ .setFrameScale(1)
+ .setPixelFormat(PixelFormat.RGBA_8888)
+ .setChildrenOnly(true)
+ .setAllowProtected(true)
+ .build();
+ final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+ SurfaceControl.captureLayers(captureArgs);
+
+ if (edgeBuffer == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Failed to capture edge of window.");
+ return null;
+ }
+
+ android.graphics.BitmapShader shader =
+ new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
+ android.graphics.Shader.TileMode.CLAMP,
+ android.graphics.Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
+
+ final Surface surface = new Surface(edgeExtensionLayer);
+ Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
+
+ startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+ startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+ startTransaction.setVisibility(edgeExtensionLayer, true);
+ finishTransaction.remove(edgeExtensionLayer);
+
+ return edgeExtensionLayer;
+ }
+
private void addBackgroundToTransition(
@NonNull SurfaceControl rootLeash,
@ColorInt int color,
@@ -628,7 +834,7 @@
final boolean isClose = Transitions.isClosingType(change.getMode());
if (isOpen) {
if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
- attachCrossProfileThunmbnailAnimation(animations, finishCallback, change,
+ attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
cornerRadius);
} else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius);
@@ -638,13 +844,14 @@
}
}
- private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+ private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
- final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
- ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
final Rect bounds = change.getEndAbsBounds();
+ // Show the right drawable depending on the user we're transitioning to.
+ final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
+ ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
- thumbnailDrawableRes, bounds);
+ thumbnailDrawable, bounds);
if (thumbnail == null) {
return;
}
@@ -732,9 +939,17 @@
}
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
+
+ Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+ if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
+ // Clip out any overflowing edge extension
+ clipRect.inset(extensionInsets);
+ t.setCrop(leash, clipRect);
+ }
+
if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
// We can only apply rounded corner if a crop is set
- t.setWindowCrop(leash, clipRect);
+ t.setCrop(leash, clipRect);
t.setCornerRadius(leash, cornerRadius);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 33a98b2..86b73fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -33,6 +33,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -123,7 +124,8 @@
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull DisplayController displayController, @NonNull Context context,
- @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
+ @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -132,7 +134,7 @@
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
- animExecutor));
+ mainHandler, animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 574a9f4..556742e 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -26,8 +26,16 @@
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6000s" />
<option name="hidden-api-checks" value="false" />
+ <option name="device-listeners"
+ value="com.android.server.wm.flicker.TraceFileReadyListener" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+ <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+ <option name="collect-on-run-ended-only" value="false" />
+ <option name="clean-up" value="true" />
+ </metrics_collector>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 6524182..9061239 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -93,7 +93,7 @@
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 20)
+ repetitions = 5)
}
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 825320b..a6caefe 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -363,6 +363,45 @@
}
@Test
+ public void testOnEligibleForLetterboxEducationActivityChanged() {
+ final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+ taskInfo1.displayId = DEFAULT_DISPLAY;
+ taskInfo1.topActivityEligibleForLetterboxEducation = false;
+ final TrackingTaskListener taskListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+
+ // Task listener sent to compat UI is null if top activity isn't eligible for letterbox
+ // education.
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+
+ // Task listener is non-null if top activity is eligible for letterbox education and task
+ // is visible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo2 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo2.displayId = taskInfo1.displayId;
+ taskInfo2.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.isVisible = true;
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+
+ // Task listener is null if task is invisible.
+ clearInvocations(mCompatUI);
+ final RunningTaskInfo taskInfo3 =
+ createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
+ taskInfo3.displayId = taskInfo1.displayId;
+ taskInfo3.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.isVisible = false;
+ mOrganizer.onTaskInfoChanged(taskInfo3);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+
+ clearInvocations(mCompatUI);
+ mOrganizer.onTaskVanished(taskInfo1);
+ verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ }
+
+ @Test
public void testOnCameraCompatActivityChanged() {
final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
@@ -375,7 +414,7 @@
// compat control.
verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
- // Task linster is non-null when request a camera compat control for a visible task.
+ // Task listener is non-null when request a camera compat control for a visible task.
clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 960c7ac..b738c47 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -75,7 +75,8 @@
screenshotSurface,
hardwareBuffer,
new WindowConfiguration(),
- new RemoteCallback((bundle) -> {}));
+ new RemoteCallback((bundle) -> {}),
+ null);
try {
doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation();
} catch (RemoteException ex) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e391713..0f4a06f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -54,7 +54,9 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
@@ -84,8 +86,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import java.util.ArrayList;
@@ -106,6 +106,7 @@
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@Before
public void setUp() {
@@ -752,7 +753,7 @@
private Transitions createTestTransitions() {
return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
- mContext, mMainExecutor, mAnimExecutor);
+ mContext, mMainExecutor, mMainHandler, mAnimExecutor);
}
//
// private class TestDisplayController extends DisplayController {
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index db3a108..dd272cd 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -287,29 +287,29 @@
std::mutex mVkLock;
};
+static bool checkSupport(AHardwareBuffer_Format format) {
+ AHardwareBuffer_Desc desc = {
+ .width = 1,
+ .height = 1,
+ .layers = 1,
+ .format = format,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
+ };
+ UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
+ return buffer != nullptr;
+}
+
bool HardwareBitmapUploader::hasFP16Support() {
- static std::once_flag sOnce;
- static bool hasFP16Support = false;
-
- // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so
- // we don't need to double-check the GLES version/extension.
- std::call_once(sOnce, []() {
- AHardwareBuffer_Desc desc = {
- .width = 1,
- .height = 1,
- .layers = 1,
- .format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
- AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
- AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
- };
- UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc);
- hasFP16Support = buffer != nullptr;
- });
-
+ static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT);
return hasFP16Support;
}
+bool HardwareBitmapUploader::has1010102Support() {
+ static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM);
+ return has101012Support;
+}
+
static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
FormatInfo formatInfo;
switch (skBitmap.info().colorType()) {
@@ -350,6 +350,19 @@
formatInfo.type = GL_UNSIGNED_BYTE;
formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
+ case kRGBA_1010102_SkColorType:
+ formatInfo.isSupported = HardwareBitmapUploader::has1010102Support();
+ if (formatInfo.isSupported) {
+ formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ } else {
+ formatInfo.type = GL_UNSIGNED_BYTE;
+ formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
+ formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
+ }
+ formatInfo.format = GL_RGBA;
+ break;
default:
ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
formatInfo.valid = false;
diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h
index ad7a95a..34f43cd 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -29,10 +29,12 @@
#ifdef __ANDROID__
static bool hasFP16Support();
+ static bool has1010102Support();
#else
static bool hasFP16Support() {
return true;
}
+ static bool has1010102Support() { return true; }
#endif
};
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/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index 3780ba0..bc6bc45 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -57,6 +57,8 @@
return ANDROID_BITMAP_FORMAT_A_8;
case kRGBA_F16_SkColorType:
return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_1010102;
default:
return ANDROID_BITMAP_FORMAT_NONE;
}
@@ -74,6 +76,8 @@
return kAlpha_8_SkColorType;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return kRGBA_F16_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return kRGBA_1010102_SkColorType;
default:
return kUnknown_SkColorType;
}
@@ -249,6 +253,9 @@
case ANDROID_BITMAP_FORMAT_RGBA_F16:
colorType = kRGBA_F16_SkColorType;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ colorType = kRGBA_1010102_SkColorType;
+ break;
default:
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
}
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index fc542c8..dd68f82 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -159,6 +159,8 @@
break;
case kRGBA_F16_SkColorType:
break;
+ case kRGBA_1010102_SkColorType:
+ break;
default:
return false;
}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 4cc05ef..1c20415 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -137,9 +137,16 @@
auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle);
SkColorType decodeColorType = brd->computeOutputColorType(colorType);
- if (decodeColorType == kRGBA_F16_SkColorType && isHardware &&
+
+ if (isHardware) {
+ if (decodeColorType == kRGBA_F16_SkColorType &&
!uirenderer::HardwareBitmapUploader::hasFP16Support()) {
- decodeColorType = kN32_SkColorType;
+ decodeColorType = kN32_SkColorType;
+ }
+ if (decodeColorType == kRGBA_1010102_SkColorType &&
+ !uirenderer::HardwareBitmapUploader::has1010102Support()) {
+ decodeColorType = kN32_SkColorType;
+ }
}
// Set up the pixel allocator
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 77f46be..33669ac 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -365,6 +365,8 @@
return kRGB_565_LegacyBitmapConfig;
case kAlpha_8_SkColorType:
return kA8_LegacyBitmapConfig;
+ case kRGBA_1010102_SkColorType:
+ return kRGBA_1010102_LegacyBitmapConfig;
case kUnknown_SkColorType:
default:
break;
@@ -374,14 +376,10 @@
SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) {
const uint8_t gConfig2ColorType[] = {
- kUnknown_SkColorType,
- kAlpha_8_SkColorType,
- kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
- kRGB_565_SkColorType,
- kARGB_4444_SkColorType,
- kN32_SkColorType,
- kRGBA_F16_SkColorType,
- kN32_SkColorType
+ kUnknown_SkColorType, kAlpha_8_SkColorType,
+ kUnknown_SkColorType, // Previously kIndex_8_SkColorType,
+ kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType,
+ kRGBA_F16_SkColorType, kN32_SkColorType, kRGBA_1010102_SkColorType,
};
if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) {
@@ -399,15 +397,12 @@
jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
const AndroidBitmapFormat config2BitmapFormat[] = {
- ANDROID_BITMAP_FORMAT_NONE,
- ANDROID_BITMAP_FORMAT_A_8,
- ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
- ANDROID_BITMAP_FORMAT_RGB_565,
- ANDROID_BITMAP_FORMAT_RGBA_4444,
- ANDROID_BITMAP_FORMAT_RGBA_8888,
- ANDROID_BITMAP_FORMAT_RGBA_F16,
- ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE
- };
+ ANDROID_BITMAP_FORMAT_NONE, ANDROID_BITMAP_FORMAT_A_8,
+ ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8
+ ANDROID_BITMAP_FORMAT_RGB_565, ANDROID_BITMAP_FORMAT_RGBA_4444,
+ ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16,
+ ANDROID_BITMAP_FORMAT_NONE, // Congfig.HARDWARE
+ ANDROID_BITMAP_FORMAT_RGBA_1010102};
return config2BitmapFormat[javaConfigId];
}
@@ -430,6 +425,9 @@
case ANDROID_BITMAP_FORMAT_RGBA_F16:
configId = kRGBA_16F_LegacyBitmapConfig;
break;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ configId = kRGBA_1010102_LegacyBitmapConfig;
+ break;
default:
break;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index ba407f2..085a905 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -34,16 +34,17 @@
// This enum must keep these int values, to match the int values
// in the java Bitmap.Config enum.
enum LegacyBitmapConfig {
- kNo_LegacyBitmapConfig = 0,
- kA8_LegacyBitmapConfig = 1,
- kIndex8_LegacyBitmapConfig = 2,
- kRGB_565_LegacyBitmapConfig = 3,
- kARGB_4444_LegacyBitmapConfig = 4,
- kARGB_8888_LegacyBitmapConfig = 5,
- kRGBA_16F_LegacyBitmapConfig = 6,
- kHardware_LegacyBitmapConfig = 7,
+ kNo_LegacyBitmapConfig = 0,
+ kA8_LegacyBitmapConfig = 1,
+ kIndex8_LegacyBitmapConfig = 2,
+ kRGB_565_LegacyBitmapConfig = 3,
+ kARGB_4444_LegacyBitmapConfig = 4,
+ kARGB_8888_LegacyBitmapConfig = 5,
+ kRGBA_16F_LegacyBitmapConfig = 6,
+ kHardware_LegacyBitmapConfig = 7,
+ kRGBA_1010102_LegacyBitmapConfig = 8,
- kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
+ kLastEnum_LegacyBitmapConfig = kRGBA_1010102_LegacyBitmapConfig
};
static void setJavaVM(JavaVM* javaVM);
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 8ad8abc..25ed935 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,19 +16,18 @@
#ifndef DRAWFRAMETASK_H
#define DRAWFRAMETASK_H
-#include <optional>
-#include <vector>
-
-#include <performance_hint_private.h>
+#include <android/performance_hint.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <utils/StrongPointer.h>
-#include "RenderTask.h"
+#include <optional>
+#include <vector>
#include "../FrameInfo.h"
#include "../Rect.h"
#include "../TreeInfo.h"
+#include "RenderTask.h"
namespace android {
namespace uirenderer {
diff --git a/libs/tracingproxy/Android.bp b/libs/tracingproxy/Android.bp
index 7126bfa..23d107b 100644
--- a/libs/tracingproxy/Android.bp
+++ b/libs/tracingproxy/Android.bp
@@ -37,6 +37,7 @@
srcs: [
":ITracingServiceProxy.aidl",
+ ":TraceReportParams.aidl",
],
shared_libs: [
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 8054fd4..f4e965f 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -125,8 +125,8 @@
boolean isAdasGnssLocationEnabledForUser(int userId);
void setAdasGnssLocationEnabledForUser(boolean enabled, int userId);
- boolean isAutoGnssSuspended();
- void setAutoGnssSuspended(boolean suspended);
+ boolean isAutomotiveGnssSuspended();
+ void setAutomotiveGnssSuspended(boolean suspended);
void addTestProvider(String name, in ProviderProperties properties,
in List<String> locationTags, String packageName, @nullable String attributionTag);
diff --git a/location/java/android/location/LastLocationRequest.java b/location/java/android/location/LastLocationRequest.java
index 73c5c82..fe0a14f 100644
--- a/location/java/android/location/LastLocationRequest.java
+++ b/location/java/android/location/LastLocationRequest.java
@@ -16,6 +16,9 @@
package android.location;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -220,8 +223,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull LastLocationRequest.Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
@@ -238,8 +242,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mLocationSettingsIgnored = locationSettingsIgnored;
return this;
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9109a18..59c989b 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
import static android.Manifest.permission.LOCATION_HARDWARE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.location.LocationRequest.createFromDeprecatedCriteria;
@@ -678,8 +679,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
- @RequiresPermission(WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public void setAdasGnssLocationEnabled(boolean enabled) {
try {
mService.setAdasGnssLocationEnabledForUser(enabled, mContext.getUser().getIdentifier());
@@ -758,45 +760,41 @@
}
/**
- * Set whether GNSS requests are suspended on the device.
+ * Set whether GNSS requests are suspended on the automotive device.
*
- * This method was added to help support power management use cases on automotive devices. More
- * specifically, it is being added to fix a suspend to RAM issue where the SoC can't go into
- * a lower power state when applications are actively requesting GNSS updates.
+ * For devices where GNSS prevents the system from going into a low power state, GNSS should
+ * be suspended right before going into the lower power state and resumed right after the device
+ * wakes up.
*
- * Ideally, the issue should be fixed at a lower layer in the stack, but this API introduces a
- * workaround in the platform layer. This API allows car specific services to halt GNSS requests
- * based on changes to the car power policy, which will in turn enable the device to go into
- * suspend.
+ * This method disables GNSS and should only be used for power management use cases such as
+ * suspend-to-RAM or suspend-to-disk.
*
* @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public void setAutoGnssSuspended(boolean suspended) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public void setAutomotiveGnssSuspended(boolean suspended) {
try {
- mService.setAutoGnssSuspended(suspended);
+ mService.setAutomotiveGnssSuspended(suspended);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Return whether GNSS requests are suspended or not.
- *
- * This method was added to help support power management use cases on automotive devices. More
- * specifically, it is being added as part of the fix for a suspend to RAM issue where the SoC
- * can't go into a lower power state when applications are actively requesting GNSS updates.
+ * Return whether GNSS requests are suspended on the automotive device.
*
* @return true if GNSS requests are suspended and false if they aren't.
*
* @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public boolean isAutoGnssSuspended() {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public boolean isAutomotiveGnssSuspended() {
try {
- return mService.isAutoGnssSuspended();
+ return mService.isAutomotiveGnssSuspended();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 587222a..59f4f5e 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,9 @@
package android.location;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -662,9 +665,10 @@
* @hide
* @deprecated LocationRequests should be treated as immutable.
*/
+ // TODO: remove WRITE_SECURE_SETTINGS.
@SystemApi
@Deprecated
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mBypass = locationSettingsIgnored;
return this;
@@ -1132,8 +1136,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
@@ -1150,8 +1155,9 @@
*
* @hide
*/
+ // TODO: remove WRITE_SECURE_SETTINGS
@SystemApi
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS})
public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
mBypass = locationSettingsIgnored;
return this;
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index aa43cfd..29888e1 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -17,12 +17,19 @@
package android.location;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A class that contains GNSS satellite position, velocity and time information at the
* same signal transmission time {@link GnssMeasurement#getReceivedSvTimeNanos()}.
@@ -64,6 +71,60 @@
private static final int HAS_TROPO = 1 << 2;
/**
+ * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Clock field is stored in the
+ * SatellitePvt.
+ */
+ private static final int HAS_ISSUE_OF_DATA_CLOCK = 1 << 3;
+
+ /**
+ * Bit mask for {@link #mFlags} indicating a valid Issue of Data, Ephemeris field is stored in
+ * the SatellitePvt.
+ */
+ private static final int HAS_ISSUE_OF_DATA_EPHEMERIS = 1 << 4;
+
+ /**
+ * Bit mask for {@link #mFlags} indicating a valid Time of Clock field is stored in the
+ * SatellitePvt.
+ */
+ private static final int HAS_TIME_OF_CLOCK = 1 << 5;
+
+ /**
+ * Bit mask for {@link #mFlags} indicating a valid Time of Ephemeris field is stored in
+ * the SatellitePvt.
+ */
+ private static final int HAS_TIME_OF_EPHEMERIS = 1 << 6;
+
+
+ /** Ephemeris demodulated from broadcast signals */
+ public static final int EPHEMERIS_SOURCE_DEMODULATED = 0;
+
+ /**
+ * Server provided Normal type ephemeris data, which is similar to broadcast ephemeris in
+ * longevity (e.g. SUPL) - lasting for few hours and providing satellite orbit and clock
+ * with accuracy of 1 - 2 meters.
+ */
+ public static final int EPHEMERIS_SOURCE_SERVER_NORMAL = 1;
+
+ /**
+ * Server provided Long-Term type ephemeris data, which lasts for many hours to several days
+ * and often provides satellite orbit and clock accuracy of 2 - 20 meters.
+ */
+ public static final int EPHEMERIS_SOURCE_SERVER_LONG_TERM = 2;
+
+ /** Other ephemeris source */
+ public static final int EPHEMERIS_SOURCE_OTHER = 3;
+
+ /**
+ * Satellite ephemeris source
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({EPHEMERIS_SOURCE_DEMODULATED, EPHEMERIS_SOURCE_SERVER_NORMAL,
+ EPHEMERIS_SOURCE_SERVER_LONG_TERM, EPHEMERIS_SOURCE_OTHER})
+ public @interface EphemerisSource {
+ }
+
+ /**
* A bitfield of flags indicating the validity of the fields in this SatellitePvt.
* The bit masks are defined in the constants with prefix HAS_*
*
@@ -83,6 +144,12 @@
private final ClockInfo mClockInfo;
private final double mIonoDelayMeters;
private final double mTropoDelayMeters;
+ private final int mTimeOfClock;
+ private final int mTimeOfEphemeris;
+ private final int mIssueOfDataClock;
+ private final int mIssueOfDataEphemeris;
+ @EphemerisSource
+ private final int mEphemerisSource;
/**
* Class containing estimates of the satellite position fields in ECEF coordinate frame.
@@ -389,13 +456,23 @@
@Nullable VelocityEcef velocityEcef,
@Nullable ClockInfo clockInfo,
double ionoDelayMeters,
- double tropoDelayMeters) {
+ double tropoDelayMeters,
+ int timeOfClock,
+ int timeOfEphemeris,
+ int issueOfDataClock,
+ int issueOfDataEphemeris,
+ @EphemerisSource int ephemerisSource) {
mFlags = flags;
mPositionEcef = positionEcef;
mVelocityEcef = velocityEcef;
mClockInfo = clockInfo;
mIonoDelayMeters = ionoDelayMeters;
mTropoDelayMeters = tropoDelayMeters;
+ mTimeOfClock = timeOfClock;
+ mTimeOfEphemeris = timeOfEphemeris;
+ mIssueOfDataClock = issueOfDataClock;
+ mIssueOfDataEphemeris = issueOfDataEphemeris;
+ mEphemerisSource = ephemerisSource;
}
/**
@@ -441,6 +518,66 @@
return mTropoDelayMeters;
}
+ /**
+ * Issue of Data, Clock.
+ *
+ * <p>This is defined in GPS ICD200 documentation (e.g.,
+ * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ *
+ * <p>This field is valid if {@link #hasIssueOfDataClock()} is true.
+ */
+ @IntRange(from = 0, to = 1023)
+ public int getIssueOfDataClock() {
+ return mIssueOfDataClock;
+ }
+
+ /**
+ * Issue of Data, Ephemeris.
+ *
+ * <p>This is defined in GPS ICD200 documentation (e.g.,
+ * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ *
+ * <p>This field is valid if {@link #hasIssueOfDataEphemeris()} is true.
+ */
+ @IntRange(from = 0, to = 255)
+ public int getIssueOfDataEphemeris() {
+ return mIssueOfDataEphemeris;
+ }
+
+ /**
+ * Time of Clock.
+ *
+ * <p>This is defined in GPS ICD200 documentation (e.g.,
+ * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ *
+ * <p>This field is valid if {@link #hasTimeOfClock()} is true.
+ */
+ @IntRange(from = 0, to = 604784)
+ public int getTimeOfClock() {
+ return mTimeOfClock;
+ }
+
+ /**
+ * Time of ephemeris.
+ *
+ * <p>This is defined in GPS ICD200 documentation (e.g.,
+ * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ *
+ * <p>This field is valid if {@link #hasTimeOfEphemeris()} is true.
+ */
+ @IntRange(from = 0, to = 604784)
+ public int getTimeOfEphemeris() {
+ return mTimeOfEphemeris;
+ }
+
+ /**
+ * Satellite ephemeris source.
+ */
+ @EphemerisSource
+ public int getEphemerisSource() {
+ return mEphemerisSource;
+ }
+
/** Returns {@code true} if {@link #getPositionEcef()}, {@link #getVelocityEcef()},
* and {@link #getClockInfo()} are valid.
*/
@@ -458,6 +595,26 @@
return (mFlags & HAS_TROPO) != 0;
}
+ /** Returns {@code true} if {@link #getIssueOfDataClock()} is valid. */
+ public boolean hasIssueOfDataClock() {
+ return (mFlags & HAS_ISSUE_OF_DATA_CLOCK) != 0;
+ }
+
+ /** Returns {@code true} if {@link #getIssueOfDataEphemeris()} is valid. */
+ public boolean hasIssueOfDataEphemeris() {
+ return (mFlags & HAS_ISSUE_OF_DATA_EPHEMERIS) != 0;
+ }
+
+ /** Returns {@code true} if {@link #getTimeOfClock()} ()} is valid. */
+ public boolean hasTimeOfClock() {
+ return (mFlags & HAS_TIME_OF_CLOCK) != 0;
+ }
+
+ /** Returns {@code true} if {@link #getTimeOfEphemeris()} is valid. */
+ public boolean hasTimeOfEphemeris() {
+ return (mFlags & HAS_TIME_OF_EPHEMERIS) != 0;
+ }
+
public static final @android.annotation.NonNull Creator<SatellitePvt> CREATOR =
new Creator<SatellitePvt>() {
@Override
@@ -465,11 +622,19 @@
public SatellitePvt createFromParcel(Parcel in) {
int flags = in.readInt();
ClassLoader classLoader = getClass().getClassLoader();
- PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class);
- VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class);
- ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class);
+ PositionEcef positionEcef = in.readParcelable(classLoader,
+ android.location.SatellitePvt.PositionEcef.class);
+ VelocityEcef velocityEcef = in.readParcelable(classLoader,
+ android.location.SatellitePvt.VelocityEcef.class);
+ ClockInfo clockInfo = in.readParcelable(classLoader,
+ android.location.SatellitePvt.ClockInfo.class);
double ionoDelayMeters = in.readDouble();
double tropoDelayMeters = in.readDouble();
+ int toc = in.readInt();
+ int toe = in.readInt();
+ int iodc = in.readInt();
+ int iode = in.readInt();
+ int ephemerisSource = in.readInt();
return new SatellitePvt(
flags,
@@ -477,7 +642,12 @@
velocityEcef,
clockInfo,
ionoDelayMeters,
- tropoDelayMeters);
+ tropoDelayMeters,
+ toc,
+ toe,
+ iodc,
+ iode,
+ ephemerisSource);
}
@Override
@@ -499,18 +669,28 @@
parcel.writeParcelable(mClockInfo, flags);
parcel.writeDouble(mIonoDelayMeters);
parcel.writeDouble(mTropoDelayMeters);
+ parcel.writeInt(mTimeOfClock);
+ parcel.writeInt(mTimeOfEphemeris);
+ parcel.writeInt(mIssueOfDataClock);
+ parcel.writeInt(mIssueOfDataEphemeris);
+ parcel.writeInt(mEphemerisSource);
}
@Override
public String toString() {
- return "SatellitePvt{"
+ return "SatellitePvt["
+ "Flags=" + mFlags
+ ", PositionEcef=" + mPositionEcef
+ ", VelocityEcef=" + mVelocityEcef
+ ", ClockInfo=" + mClockInfo
+ ", IonoDelayMeters=" + mIonoDelayMeters
+ ", TropoDelayMeters=" + mTropoDelayMeters
- + "}";
+ + ", TimeOfClock=" + mTimeOfClock
+ + ", TimeOfEphemeris=" + mTimeOfEphemeris
+ + ", IssueOfDataClock=" + mIssueOfDataClock
+ + ", IssueOfDataEphemeris=" + mIssueOfDataEphemeris
+ + ", EphemerisSource=" + mEphemerisSource
+ + "]";
}
/**
@@ -527,12 +707,18 @@
@Nullable private ClockInfo mClockInfo;
private double mIonoDelayMeters;
private double mTropoDelayMeters;
+ private int mTimeOfClock;
+ private int mTimeOfEphemeris;
+ private int mIssueOfDataClock;
+ private int mIssueOfDataEphemeris;
+ @EphemerisSource
+ private int mEphemerisSource = EPHEMERIS_SOURCE_OTHER;
/**
* Set position ECEF.
*
* @param positionEcef position ECEF object
- * @return Builder builder object
+ * @return builder object
*/
@NonNull
public Builder setPositionEcef(
@@ -546,7 +732,7 @@
* Set velocity ECEF.
*
* @param velocityEcef velocity ECEF object
- * @return Builder builder object
+ * @return builder object
*/
@NonNull
public Builder setVelocityEcef(
@@ -560,7 +746,7 @@
* Set clock info.
*
* @param clockInfo clock info object
- * @return Builder builder object
+ * @return builder object
*/
@NonNull
public Builder setClockInfo(
@@ -580,7 +766,7 @@
* Set ionospheric delay in meters.
*
* @param ionoDelayMeters ionospheric delay (meters)
- * @return Builder builder object
+ * @return builder object
*/
@NonNull
public Builder setIonoDelayMeters(
@@ -594,7 +780,7 @@
* Set tropospheric delay in meters.
*
* @param tropoDelayMeters tropospheric delay (meters)
- * @return Builder builder object
+ * @return builder object
*/
@NonNull
public Builder setTropoDelayMeters(
@@ -605,6 +791,80 @@
}
/**
+ * Set time of clock in seconds.
+ *
+ * @param timeOfClock time of clock (seconds)
+ * @return builder object
+ */
+ @NonNull
+ public Builder setTimeOfClock(@IntRange(from = 0, to = 604784) int timeOfClock) {
+ Preconditions.checkArgumentInRange(timeOfClock, 0, 604784, "timeOfClock");
+ mTimeOfClock = timeOfClock;
+ mFlags = (byte) (mFlags | HAS_TIME_OF_CLOCK);
+ return this;
+ }
+
+ /**
+ * Set time of ephemeris in seconds.
+ *
+ * @param timeOfEphemeris time of ephemeris (seconds)
+ * @return builder object
+ */
+ @NonNull
+ public Builder setTimeOfEphemeris(@IntRange(from = 0, to = 604784) int timeOfEphemeris) {
+ Preconditions.checkArgumentInRange(timeOfEphemeris, 0, 604784, "timeOfEphemeris");
+ mTimeOfEphemeris = timeOfEphemeris;
+ mFlags = (byte) (mFlags | HAS_TIME_OF_EPHEMERIS);
+ return this;
+ }
+
+ /**
+ * Set issue of data, clock.
+ *
+ * @param issueOfDataClock issue of data, clock.
+ * @return builder object
+ */
+ @NonNull
+ public Builder setIssueOfDataClock(@IntRange(from = 0, to = 1023) int issueOfDataClock) {
+ Preconditions.checkArgumentInRange(issueOfDataClock, 0, 1023, "issueOfDataClock");
+ mIssueOfDataClock = issueOfDataClock;
+ mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_CLOCK);
+ return this;
+ }
+
+ /**
+ * Set issue of data, ephemeris.
+ *
+ * @param issueOfDataEphemeris issue of data, ephemeris.
+ * @return builder object
+ */
+ @NonNull
+ public Builder setIssueOfDataEphemeris(
+ @IntRange(from = 0, to = 255) int issueOfDataEphemeris) {
+ Preconditions.checkArgumentInRange(issueOfDataEphemeris, 0, 255,
+ "issueOfDataEphemeris");
+ mIssueOfDataEphemeris = issueOfDataEphemeris;
+ mFlags = (byte) (mFlags | HAS_ISSUE_OF_DATA_EPHEMERIS);
+ return this;
+ }
+
+ /**
+ * Set satellite ephemeris source.
+ *
+ * @param ephemerisSource satellite ephemeris source
+ * @return builder object
+ */
+ @NonNull
+ public Builder setEphemerisSource(@EphemerisSource int ephemerisSource) {
+ Preconditions.checkArgument(ephemerisSource == EPHEMERIS_SOURCE_DEMODULATED
+ || ephemerisSource == EPHEMERIS_SOURCE_SERVER_NORMAL
+ || ephemerisSource == EPHEMERIS_SOURCE_SERVER_LONG_TERM
+ || ephemerisSource == EPHEMERIS_SOURCE_OTHER);
+ mEphemerisSource = ephemerisSource;
+ return this;
+ }
+
+ /**
* Build SatellitePvt object.
*
* @return instance of SatellitePvt
@@ -612,7 +872,9 @@
@NonNull
public SatellitePvt build() {
return new SatellitePvt(mFlags, mPositionEcef, mVelocityEcef, mClockInfo,
- mIonoDelayMeters, mTropoDelayMeters);
+ mIonoDelayMeters, mTropoDelayMeters, mTimeOfClock, mTimeOfEphemeris,
+ mIssueOfDataClock, mIssueOfDataEphemeris,
+ mEphemerisSource);
}
}
}
diff --git a/media/Android.bp b/media/Android.bp
index fcdfd72..5aedcfb 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -108,6 +108,11 @@
vndk: {
enabled: true,
},
+ min_sdk_version: "29",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.bluetooth",
+ ],
},
},
}
diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java
index 11371b1..df648be 100644
--- a/media/java/android/media/AudioDescriptor.java
+++ b/media/java/android/media/AudioDescriptor.java
@@ -18,16 +18,21 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
/**
* The AudioDescriptor contains the information to describe the audio playback/capture
* capabilities. The capabilities are described by a byte array, which is defined by a
* particular standard. This is used when the format is unrecognized to the platform.
*/
-public class AudioDescriptor {
+public class AudioDescriptor implements Parcelable {
/**
* The audio standard is not specified.
*/
@@ -49,7 +54,15 @@
private final byte[] mDescriptor;
private final int mEncapsulationType;
- AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) {
+ /**
+ * @hide
+ * Constructor from standard, encapsulation type and descriptor
+ * @param standard the standard of the audio descriptor
+ * @param encapsulationType the encapsulation type of the audio descriptor
+ * @param descriptor the audio descriptor
+ */
+ @SystemApi
+ public AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) {
mStandard = standard;
mEncapsulationType = encapsulationType;
mDescriptor = descriptor;
@@ -87,4 +100,66 @@
public @AudioProfile.EncapsulationType int getEncapsulationType() {
return mEncapsulationType;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStandard, mEncapsulationType, Arrays.hashCode(mDescriptor));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AudioDescriptor that = (AudioDescriptor) o;
+ return ((mStandard == that.mStandard)
+ && (mEncapsulationType == that.mEncapsulationType)
+ && (Arrays.equals(mDescriptor, that.mDescriptor)));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append("standard=" + mStandard);
+ sb.append(", encapsulation type=" + mEncapsulationType);
+ if (mDescriptor != null && mDescriptor.length > 0) {
+ sb.append(", descriptor=").append(Arrays.toString(mDescriptor));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStandard);
+ dest.writeInt(mEncapsulationType);
+ dest.writeByteArray(mDescriptor);
+ }
+
+ private AudioDescriptor(@NonNull Parcel in) {
+ mStandard = in.readInt();
+ mEncapsulationType = in.readInt();
+ mDescriptor = in.createByteArray();
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioDescriptor> CREATOR =
+ new Parcelable.Creator<AudioDescriptor>() {
+ /**
+ * Rebuilds an AudioDescriptor previously stored with writeToParcel().
+ * @param p Parcel object to read the AudioDescriptor from
+ * @return a new AudioDescriptor created from the data in the parcel
+ */
+ public AudioDescriptor createFromParcel(Parcel p) {
+ return new AudioDescriptor(p);
+ }
+
+ public AudioDescriptor[] newArray(int size) {
+ return new AudioDescriptor[size];
+ }
+ };
}
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index 1448c49..af3c295 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -18,12 +18,16 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
/**
@@ -65,16 +69,27 @@
* The unique address of the device. Some devices don't have addresses, only an empty string.
*/
private final @NonNull String mAddress;
-
+ /**
+ * The non-unique name of the device. Some devices don't have names, only an empty string.
+ * Should not be used as a unique identifier for a device.
+ */
+ private final @NonNull String mName;
/**
* Is input or output device
*/
private final @Role int mRole;
-
/**
* The internal audio device type
*/
private final int mNativeType;
+ /**
+ * List of AudioProfiles supported by the device
+ */
+ private final @NonNull List<AudioProfile> mAudioProfiles;
+ /**
+ * List of AudioDescriptors supported by the device
+ */
+ private final @NonNull List<AudioDescriptor> mAudioDescriptors;
/**
* @hide
@@ -88,7 +103,10 @@
mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
mType = deviceInfo.getType();
mAddress = deviceInfo.getAddress();
+ mName = String.valueOf(deviceInfo.getProductName());
mNativeType = deviceInfo.getInternalType();
+ mAudioProfiles = deviceInfo.getAudioProfiles();
+ mAudioDescriptors = deviceInfo.getAudioDescriptors();
}
/**
@@ -100,7 +118,24 @@
*/
@SystemApi
public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
- @NonNull String address) {
+ @NonNull String address) {
+ this(role, type, address, "", new ArrayList<>(), new ArrayList<>());
+ }
+
+ /**
+ * @hide
+ * Constructor with specification of all attributes
+ * @param role indicates input or output role
+ * @param type the device type, as defined in {@link AudioDeviceInfo}
+ * @param address the address of the device, or an empty string for devices without one
+ * @param name the name of the device, or an empty string for devices without one
+ * @param profiles the list of AudioProfiles supported by the device
+ * @param descriptors the list of AudioDescriptors supported by the device
+ */
+ @SystemApi
+ public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
+ @NonNull String address, @NonNull String name, @NonNull List<AudioProfile> profiles,
+ @NonNull List<AudioDescriptor> descriptors) {
Objects.requireNonNull(address);
if (role != ROLE_OUTPUT && role != ROLE_INPUT) {
throw new IllegalArgumentException("Invalid role " + role);
@@ -118,19 +153,37 @@
mRole = role;
mType = type;
mAddress = address;
+ mName = name;
+ mAudioProfiles = profiles;
+ mAudioDescriptors = descriptors;
}
/**
* @hide
- * Constructor from internal device type and address
- * @param type the internal device type, as defined in {@link AudioSystem}
+ * Constructor called from AudioSystem JNI when creating an AudioDeviceAttributes from a native
+ * AudioDeviceTypeAddr instance.
+ * @param nativeType the internal device type, as defined in {@link AudioSystem}
* @param address the address of the device, or an empty string for devices without one
*/
public AudioDeviceAttributes(int nativeType, @NonNull String address) {
+ this(nativeType, address, "");
+ }
+
+ /**
+ * @hide
+ * Constructor called from BtHelper to connect or disconnect a Bluetooth device.
+ * @param nativeType the internal device type, as defined in {@link AudioSystem}
+ * @param address the address of the device, or an empty string for devices without one
+ * @param name the name of the device, or an empty string for devices without one
+ */
+ public AudioDeviceAttributes(int nativeType, @NonNull String address, @NonNull String name) {
mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT;
mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
mAddress = address;
+ mName = name;
mNativeType = nativeType;
+ mAudioProfiles = new ArrayList<>();
+ mAudioDescriptors = new ArrayList<>();
}
/**
@@ -165,6 +218,16 @@
/**
* @hide
+ * Returns the name of the audio device, or an empty string for devices without one
+ * @return the device name
+ */
+ @SystemApi
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ /**
+ * @hide
* Returns the internal device type of a device
* @return the internal device type
*/
@@ -172,9 +235,29 @@
return mNativeType;
}
+ /**
+ * @hide
+ * Returns the list of AudioProfiles supported by the device
+ * @return the list of AudioProfiles
+ */
+ @SystemApi
+ public @NonNull List<AudioProfile> getAudioProfiles() {
+ return mAudioProfiles;
+ }
+
+ /**
+ * @hide
+ * Returns the list of AudioDescriptors supported by the device
+ * @return the list of AudioDescriptors
+ */
+ @SystemApi
+ public @NonNull List<AudioDescriptor> getAudioDescriptors() {
+ return mAudioDescriptors;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mRole, mType, mAddress);
+ return Objects.hash(mRole, mType, mAddress, mName, mAudioProfiles, mAudioDescriptors);
}
@Override
@@ -185,6 +268,25 @@
AudioDeviceAttributes that = (AudioDeviceAttributes) o;
return ((mRole == that.mRole)
&& (mType == that.mType)
+ && mAddress.equals(that.mAddress)
+ && mName.equals(that.mName)
+ && mAudioProfiles.equals(that.mAudioProfiles)
+ && mAudioDescriptors.equals(that.mAudioDescriptors));
+ }
+
+ /**
+ * Returns true if the role, type and address are equal. Called to compare with an
+ * AudioDeviceAttributes that was created from a native AudioDeviceTypeAddr instance.
+ * @param o object to compare with
+ * @return whether role, type and address are equal
+ */
+ public boolean equalTypeAddress(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AudioDeviceAttributes that = (AudioDeviceAttributes) o;
+ return ((mRole == that.mRole)
+ && (mType == that.mType)
&& mAddress.equals(that.mAddress));
}
@@ -199,7 +301,10 @@
+ " role:" + roleToString(mRole)
+ " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType)
: AudioSystem.getInputDeviceName(mNativeType))
- + " addr:" + mAddress);
+ + " addr:" + mAddress
+ + " name:" + mName
+ + " profiles:" + mAudioProfiles.toString()
+ + " descriptors:" + mAudioDescriptors.toString());
}
@Override
@@ -212,14 +317,26 @@
dest.writeInt(mRole);
dest.writeInt(mType);
dest.writeString(mAddress);
+ dest.writeString(mName);
dest.writeInt(mNativeType);
+ dest.writeParcelableArray(
+ mAudioProfiles.toArray(new AudioProfile[mAudioProfiles.size()]), flags);
+ dest.writeParcelableArray(
+ mAudioDescriptors.toArray(new AudioDescriptor[mAudioDescriptors.size()]), flags);
}
private AudioDeviceAttributes(@NonNull Parcel in) {
mRole = in.readInt();
mType = in.readInt();
mAddress = in.readString();
+ mName = in.readString();
mNativeType = in.readInt();
+ AudioProfile[] audioProfilesArray =
+ in.readParcelableArray(AudioProfile.class.getClassLoader(), AudioProfile.class);
+ mAudioProfiles = new ArrayList<AudioProfile>(Arrays.asList(audioProfilesArray));
+ AudioDescriptor[] audioDescriptorsArray = in.readParcelableArray(
+ AudioDescriptor.class.getClassLoader(), AudioDescriptor.class);
+ mAudioDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(audioDescriptorsArray));
}
public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR =
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index dd17dc6..3d08959 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -421,7 +421,7 @@
*/
public CharSequence getProductName() {
String portName = mPort.name();
- return portName.length() != 0 ? portName : android.os.Build.MODEL;
+ return (portName != null && portName.length() != 0) ? portName : android.os.Build.MODEL;
}
/**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c4cef4c..cdc3163 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -181,6 +181,22 @@
public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
/**
+ * @hide Broadcast intent when the volume for a particular stream type changes.
+ * Includes the stream, the new volume and previous volumes.
+ * Notes:
+ * - for internal platform use only, do not make public,
+ * - never used for "remote" volume changes
+ *
+ * @see #EXTRA_VOLUME_STREAM_TYPE
+ * @see #EXTRA_VOLUME_STREAM_VALUE
+ * @see #EXTRA_PREV_VOLUME_STREAM_VALUE
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @SuppressLint("ActionValue")
+ public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
+
+ /**
* @hide Broadcast intent when the devices for a particular stream type changes.
* Includes the stream, the new devices and previous devices.
* Notes:
@@ -244,7 +260,8 @@
/**
* @hide The stream type for the volume changed intent.
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
/**
@@ -261,7 +278,8 @@
/**
* @hide The volume associated with the stream for the volume changed intent.
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @SuppressLint("ActionValue")
public static final String EXTRA_VOLUME_STREAM_VALUE =
"android.media.EXTRA_VOLUME_STREAM_VALUE";
@@ -368,7 +386,7 @@
public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
/** @hide Used to identify the volume of audio streams for phone calls when connected
* to bluetooth */
- @UnsupportedAppUsage
+ @SystemApi
public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
/** @hide Used to identify the volume of audio streams for enforced system sounds
* in certain countries (e.g camera in Japan) */
@@ -544,6 +562,7 @@
* Indicates the volume set/adjust call is for Bluetooth absolute volume
* @hide
*/
+ @SystemApi
public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
/**
@@ -5855,7 +5874,7 @@
return false;
}
- /**
+ /**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
* @param state new connection state: 1 connected, 0 disconnected
@@ -5864,10 +5883,29 @@
*/
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
+ public void setWiredDeviceConnectionState(int device, int state, String address,
+ String name) {
+ final IAudioService service = getService();
+ int role = isOutputDevice(device)
+ ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT;
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+ role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address,
+ name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/);
+ setWiredDeviceConnectionState(attributes, state);
+ }
+
+ /**
+ * Indicate wired accessory connection state change and attributes.
+ * @param state new connection state: 1 connected, 0 disconnected
+ * @param attributes attributes of the connected device
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, int state) {
final IAudioService service = getService();
try {
- service.setWiredDeviceConnectionState(type, state, address, name,
+ service.setWiredDeviceConnectionState(attributes, state,
mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -5900,13 +5938,14 @@
* @param newDevice Bluetooth device connected or null if there is no new devices
* @param previousDevice Bluetooth device disconnected or null if there is no disconnected
* devices
- * @param info contain all info related to the device. {@link BtProfileConnectionInfo}
+ * @param info contain all info related to the device. {@link BluetoothProfileConnectionInfo}
* {@hide}
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK)
public void handleBluetoothActiveDeviceChanged(@Nullable BluetoothDevice newDevice,
- @Nullable BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) {
+ @Nullable BluetoothDevice previousDevice,
+ @NonNull BluetoothProfileConnectionInfo info) {
final IAudioService service = getService();
try {
service.handleBluetoothActiveDeviceChanged(newDevice, previousDevice, info);
@@ -7242,6 +7281,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 +7732,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 +8436,4 @@
return mHandler;
}
}
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index ae8d0a5..5c5f837 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,10 +18,14 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -33,7 +37,7 @@
* be reported in different audio profiles. The application can choose any of the encapsulation
* types.
*/
-public class AudioProfile {
+public class AudioProfile implements Parcelable {
/**
* No encapsulation type is specified.
*/
@@ -57,9 +61,19 @@
private final int[] mChannelIndexMasks;
private final int mEncapsulationType;
- AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks,
- @NonNull int[] channelIndexMasks,
- int encapsulationType) {
+ /**
+ * @hide
+ * Constructor from format, sampling rates, channel masks, channel index masks and
+ * encapsulation type.
+ * @param format the audio format
+ * @param samplingRates the supported sampling rates
+ * @param channelMasks the supported channel masks
+ * @param channelIndexMasks the supported channel index masks
+ * @param encapsulationType the encapsulation type of the encoding format
+ */
+ @SystemApi
+ public AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks,
+ @NonNull int[] channelIndexMasks, int encapsulationType) {
mFormat = format;
mSamplingRates = samplingRates;
mChannelMasks = channelMasks;
@@ -114,6 +128,26 @@
}
@Override
+ public int hashCode() {
+ return Objects.hash(mFormat, Arrays.hashCode(mSamplingRates),
+ Arrays.hashCode(mChannelMasks), Arrays.hashCode(mChannelIndexMasks),
+ mEncapsulationType);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ AudioProfile that = (AudioProfile) o;
+ return ((mFormat == that.mFormat)
+ && (hasIdenticalElements(mSamplingRates, that.mSamplingRates))
+ && (hasIdenticalElements(mChannelMasks, that.mChannelMasks))
+ && (hasIdenticalElements(mChannelIndexMasks, that.mChannelIndexMasks))
+ && (mEncapsulationType == that.mEncapsulationType));
+ }
+
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
@@ -126,6 +160,7 @@
if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
}
+ sb.append(", encapsulation type=" + mEncapsulationType);
sb.append("}");
return sb.toString();
}
@@ -137,4 +172,50 @@
return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt))
.collect(Collectors.joining(", "));
}
+
+ private static boolean hasIdenticalElements(int[] array1, int[] array2) {
+ int[] sortedArray1 = Arrays.copyOf(array1, array1.length);
+ Arrays.sort(sortedArray1);
+ int[] sortedArray2 = Arrays.copyOf(array2, array2.length);
+ Arrays.sort(sortedArray2);
+ return Arrays.equals(sortedArray1, sortedArray2);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mFormat);
+ dest.writeIntArray(mSamplingRates);
+ dest.writeIntArray(mChannelMasks);
+ dest.writeIntArray(mChannelIndexMasks);
+ dest.writeInt(mEncapsulationType);
+ }
+
+ private AudioProfile(@NonNull Parcel in) {
+ mFormat = in.readInt();
+ mSamplingRates = in.createIntArray();
+ mChannelMasks = in.createIntArray();
+ mChannelIndexMasks = in.createIntArray();
+ mEncapsulationType = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioProfile> CREATOR =
+ new Parcelable.Creator<AudioProfile>() {
+ /**
+ * Rebuilds an AudioProfile previously stored with writeToParcel().
+ * @param p Parcel object to read the AudioProfile from
+ * @return a new AudioProfile created from the data in the parcel
+ */
+ public AudioProfile createFromParcel(Parcel p) {
+ return new AudioProfile(p);
+ }
+
+ public AudioProfile[] newArray(int size) {
+ return new AudioProfile[size];
+ }
+ };
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 306479a..536b4ad 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -26,10 +26,12 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.media.audio.common.AidlConversion;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
import android.os.Build;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.Vibrator;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -1555,9 +1557,24 @@
* {@link #AUDIO_STATUS_ERROR} or {@link #AUDIO_STATUS_SERVER_DIED}
*/
@UnsupportedAppUsage
- public static native int setDeviceConnectionState(int device, int state,
- String device_address, String device_name,
- int codecFormat);
+ public static int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
+ int codecFormat) {
+ android.media.audio.common.AudioPort port =
+ AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes);
+ Parcel parcel = Parcel.obtain();
+ port.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ try {
+ return setDeviceConnectionState(state, parcel, codecFormat);
+ } finally {
+ parcel.recycle();
+ }
+ }
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static native int setDeviceConnectionState(int state, Parcel parcel, int codecFormat);
/** @hide */
@UnsupportedAppUsage
public static native int getDeviceConnectionState(int device, String device_address);
@@ -1871,6 +1888,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 +2138,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/BtProfileConnectionInfo.aidl b/media/java/android/media/BluetoothProfileConnectionInfo.aidl
similarity index 93%
rename from media/java/android/media/BtProfileConnectionInfo.aidl
rename to media/java/android/media/BluetoothProfileConnectionInfo.aidl
index 047f06b..0617084 100644
--- a/media/java/android/media/BtProfileConnectionInfo.aidl
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.aidl
@@ -16,5 +16,5 @@
package android.media;
-parcelable BtProfileConnectionInfo;
+parcelable BluetoothProfileConnectionInfo;
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
similarity index 60%
rename from media/java/android/media/BtProfileConnectionInfo.java
rename to media/java/android/media/BluetoothProfileConnectionInfo.java
index d1bb41e..c148846 100644
--- a/media/java/android/media/BtProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -15,40 +15,25 @@
*/
package android.media;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothProfile;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Contains information about Bluetooth profile connection state changed
* {@hide}
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-public final class BtProfileConnectionInfo implements Parcelable {
- /** @hide */
- @IntDef({
- BluetoothProfile.A2DP,
- BluetoothProfile.A2DP_SINK,
- BluetoothProfile.HEADSET, // Can only be set by BtHelper
- BluetoothProfile.HEARING_AID,
- BluetoothProfile.LE_AUDIO,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface BtProfile {}
-
- private final @BtProfile int mProfile;
+public final class BluetoothProfileConnectionInfo implements Parcelable {
+ private final int mProfile;
private final boolean mSupprNoisy;
private final int mVolume;
private final boolean mIsLeOutput;
- private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume,
- boolean isLeOutput) {
+ private BluetoothProfileConnectionInfo(int profile, boolean suppressNoisyIntent,
+ int volume, boolean isLeOutput) {
mProfile = profile;
mSupprNoisy = suppressNoisyIntent;
mVolume = volume;
@@ -59,21 +44,21 @@
* Constructor used by BtHelper when a profile is connected
* {@hide}
*/
- public BtProfileConnectionInfo(@BtProfile int profile) {
+ public BluetoothProfileConnectionInfo(int profile) {
this(profile, false, -1, false);
}
- public static final @NonNull Parcelable.Creator<BtProfileConnectionInfo> CREATOR =
- new Parcelable.Creator<BtProfileConnectionInfo>() {
+ public static final @NonNull Parcelable.Creator<BluetoothProfileConnectionInfo> CREATOR =
+ new Parcelable.Creator<BluetoothProfileConnectionInfo>() {
@Override
- public BtProfileConnectionInfo createFromParcel(Parcel source) {
- return new BtProfileConnectionInfo(source.readInt(), source.readBoolean(),
- source.readInt(), source.readBoolean());
+ public BluetoothProfileConnectionInfo createFromParcel(Parcel source) {
+ return new BluetoothProfileConnectionInfo(source.readInt(),
+ source.readBoolean(), source.readInt(), source.readBoolean());
}
@Override
- public BtProfileConnectionInfo[] newArray(int size) {
- return new BtProfileConnectionInfo[size];
+ public BluetoothProfileConnectionInfo[] newArray(int size) {
+ return new BluetoothProfileConnectionInfo[size];
}
};
@@ -98,10 +83,10 @@
*
* @param volume of device -1 to ignore value
*/
- public static @NonNull BtProfileConnectionInfo a2dpInfo(boolean suppressNoisyIntent,
- int volume) {
- return new BtProfileConnectionInfo(BluetoothProfile.A2DP, suppressNoisyIntent, volume,
- false);
+ public static @NonNull BluetoothProfileConnectionInfo createA2dpInfo(
+ boolean suppressNoisyIntent, int volume) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.A2DP, suppressNoisyIntent,
+ volume, false);
}
/**
@@ -110,8 +95,8 @@
*
* @param volume of device -1 to ignore value
*/
- public static @NonNull BtProfileConnectionInfo a2dpSinkInfo(int volume) {
- return new BtProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false);
+ public static @NonNull BluetoothProfileConnectionInfo createA2dpSinkInfo(int volume) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.A2DP_SINK, true, volume, false);
}
/**
@@ -120,9 +105,10 @@
* @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
* intent will not be sent.
*/
- public static @NonNull BtProfileConnectionInfo hearingAidInfo(boolean suppressNoisyIntent) {
- return new BtProfileConnectionInfo(BluetoothProfile.HEARING_AID, suppressNoisyIntent, -1,
- false);
+ public static @NonNull BluetoothProfileConnectionInfo createHearingAidInfo(
+ boolean suppressNoisyIntent) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.HEARING_AID, suppressNoisyIntent,
+ -1, false);
}
/**
@@ -133,16 +119,16 @@
*
* @param isLeOutput if true mean the device is an output device, if false it's an input device
*/
- public static @NonNull BtProfileConnectionInfo leAudio(boolean suppressNoisyIntent,
- boolean isLeOutput) {
- return new BtProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent, -1,
- isLeOutput);
+ public static @NonNull BluetoothProfileConnectionInfo createLeAudioInfo(
+ boolean suppressNoisyIntent, boolean isLeOutput) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent,
+ -1, isLeOutput);
}
/**
* @return The profile connection
*/
- public @BtProfile int getProfile() {
+ public int getProfile() {
return mProfile;
}
@@ -150,7 +136,7 @@
* @return {@code true} if {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be
* sent
*/
- public boolean getSuppressNoisyIntent() {
+ public boolean isSuppressNoisyIntent() {
return mSupprNoisy;
}
@@ -167,7 +153,7 @@
* @return {@code true} is the LE device is an output device, {@code false} if it's an input
* device
*/
- public boolean getIsLeOutput() {
+ public boolean isLeOutput() {
return mIsLeOutput;
}
}
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..3e26fc6 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/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 96199a9..fec14de 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -25,7 +25,7 @@
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
import android.media.IAudioRoutesObserver;
@@ -215,8 +215,7 @@
IRingtonePlayer getRingtonePlayer();
int getUiSoundsStreamType();
- void setWiredDeviceConnectionState(int type, int state, String address, String name,
- String caller);
+ void setWiredDeviceConnectionState(in AudioDeviceAttributes aa, int state, String caller);
@UnsupportedAppUsage
AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
@@ -276,7 +275,7 @@
oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
void handleBluetoothActiveDeviceChanged(in BluetoothDevice newDevice,
- in BluetoothDevice previousDevice, in BtProfileConnectionInfo info);
+ in BluetoothDevice previousDevice, in BluetoothProfileConnectionInfo info);
oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,
in IAudioPolicyCallback pcb);
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 5113dc2..71dc2a7 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -18,6 +18,7 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
+import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
/**
@@ -27,7 +28,8 @@
void notifySessionCreated(int requestId, in RoutingSessionInfo session);
void notifySessionUpdated(in RoutingSessionInfo session);
void notifySessionReleased(in RoutingSessionInfo session);
- void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
+ void notifyDiscoveryPreferenceChanged(String packageName,
+ in RouteDiscoveryPreference discoveryPreference);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index e2e48d3..5f02a43 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -43,6 +43,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.StampedLock;
/**
* <p>The ImageReader class allows direct application access to image data
@@ -675,7 +676,8 @@
* If no handler specified and the calling thread has no looper.
*/
public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) {
- synchronized (mListenerLock) {
+ long writeStamp = mListenerLock.writeLock();
+ try {
if (listener != null) {
Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
if (looper == null) {
@@ -691,6 +693,8 @@
mListenerExecutor = null;
}
mListener = listener;
+ } finally {
+ mListenerLock.unlockWrite(writeStamp);
}
}
@@ -713,9 +717,12 @@
throw new IllegalArgumentException("executor must not be null");
}
- synchronized (mListenerLock) {
+ long writeStamp = mListenerLock.writeLock();
+ try {
mListenerExecutor = executor;
mListener = listener;
+ } finally {
+ mListenerLock.unlockWrite(writeStamp);
}
}
@@ -731,6 +738,8 @@
/**
* Callback that is called when a new image is available from ImageReader.
*
+ * This callback must not modify or close the passed {@code reader}.
+ *
* @param reader the ImageReader the callback is associated with.
* @see ImageReader
* @see Image
@@ -889,28 +898,41 @@
return;
}
- final Executor executor;
- final OnImageAvailableListener listener;
- synchronized (ir.mListenerLock) {
- executor = ir.mListenerExecutor;
- listener = ir.mListener;
- }
- final boolean isReaderValid;
synchronized (ir.mCloseLock) {
- isReaderValid = ir.mIsReaderValid;
+ if (!ir.mIsReaderValid) {
+ // It's dangerous to fire onImageAvailable() callback when the ImageReader
+ // is being closed, as application could acquire next image in the
+ // onImageAvailable() callback.
+ return;
+ }
}
- // It's dangerous to fire onImageAvailable() callback when the ImageReader
- // is being closed, as application could acquire next image in the
- // onImageAvailable() callback.
- if (executor != null && listener != null && isReaderValid) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- listener.onImageAvailable(ir);
- }
- });
+ final Executor executor;
+ final long readStamp = ir.mListenerLock.readLock();
+ try {
+ executor = ir.mListenerExecutor;
+ if (executor == null) {
+ return;
+ }
+ } finally {
+ ir.mListenerLock.unlockRead(readStamp);
}
+
+ executor.execute(() -> {
+ // Acquire readlock to ensure that the ImageReader does not change its
+ // state while a listener is actively processing.
+ final long rStamp = ir.mListenerLock.readLock();
+ try {
+ // Fire onImageAvailable of the latest non-null listener
+ // This ensures that if the listener changes while messages are in queue, the
+ // in-flight messages will call onImageAvailable of the new listener instead
+ if (ir.mListener != null) {
+ ir.mListener.onImageAvailable(ir);
+ }
+ } finally {
+ ir.mListenerLock.unlockRead(rStamp);
+ }
+ });
}
/**
@@ -1070,7 +1092,7 @@
private Surface mSurface;
private int mEstimatedNativeAllocBytes;
- private final Object mListenerLock = new Object();
+ private final StampedLock mListenerLock = new StampedLock();
private final Object mCloseLock = new Object();
private boolean mIsReaderValid = false;
private OnImageAvailableListener mListener;
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/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa6..ee0293d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -34,6 +34,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Describes the properties of a route.
@@ -340,10 +341,12 @@
@ConnectionState
final int mConnectionState;
final String mClientPackageName;
+ final String mPackageName;
final int mVolumeHandling;
final int mVolumeMax;
final int mVolume;
final String mAddress;
+ final Set<String> mDeduplicationIds;
final Bundle mExtras;
final String mProviderId;
@@ -357,10 +360,12 @@
mDescription = builder.mDescription;
mConnectionState = builder.mConnectionState;
mClientPackageName = builder.mClientPackageName;
+ mPackageName = builder.mPackageName;
mVolumeHandling = builder.mVolumeHandling;
mVolumeMax = builder.mVolumeMax;
mVolume = builder.mVolume;
mAddress = builder.mAddress;
+ mDeduplicationIds = builder.mDeduplicationIds;
mExtras = builder.mExtras;
mProviderId = builder.mProviderId;
}
@@ -375,10 +380,12 @@
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mConnectionState = in.readInt();
mClientPackageName = in.readString();
+ mPackageName = in.readString();
mVolumeHandling = in.readInt();
mVolumeMax = in.readInt();
mVolume = in.readInt();
mAddress = in.readString();
+ mDeduplicationIds = Set.of(in.readStringArray());
mExtras = in.readBundle();
mProviderId = in.readString();
}
@@ -486,6 +493,17 @@
}
/**
+ * Gets the package name of the provider that published the route.
+ * <p>
+ * It is set by the system service.
+ * @hide
+ */
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Gets information about how volume is handled on the route.
*
* @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
@@ -518,6 +536,18 @@
return mAddress;
}
+ /**
+ * Gets the Deduplication ID of the route if available.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Set<String> getDeduplicationIds() {
+ return mDeduplicationIds;
+ }
+
+ /**
+ * Gets an optional bundle with extra data.
+ */
@Nullable
public Bundle getExtras() {
return mExtras == null ? null : new Bundle(mExtras);
@@ -549,7 +579,7 @@
* Returns if the route has at least one of the specified route features.
*
* @param features the list of route features to consider
- * @return true if the route has at least one feature in the list
+ * @return {@code true} if the route has at least one feature in the list
* @hide
*/
public boolean hasAnyFeatures(@NonNull Collection<String> features) {
@@ -563,6 +593,21 @@
}
/**
+ * Returns if the route has all the specified route features.
+ *
+ * @hide
+ */
+ public boolean hasAllFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (!getFeatures().contains(feature)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if the route info has all of the required field.
* A route is valid if and only if it is obtained from
* {@link com.android.server.media.MediaRouterService}.
@@ -596,10 +641,12 @@
&& Objects.equals(mDescription, other.mDescription)
&& (mConnectionState == other.mConnectionState)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mPackageName, other.mPackageName)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
&& (mVolume == other.mVolume)
&& Objects.equals(mAddress, other.mAddress)
+ && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
&& Objects.equals(mProviderId, other.mProviderId);
}
@@ -607,8 +654,8 @@
public int hashCode() {
// Note: mExtras is not included.
return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
- mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
- mAddress, mProviderId);
+ mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
+ mVolume, mAddress, mDeduplicationIds, mProviderId);
}
@Override
@@ -626,6 +673,7 @@
.append(", volumeHandling=").append(getVolumeHandling())
.append(", volumeMax=").append(getVolumeMax())
.append(", volume=").append(getVolume())
+ .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
.append(", providerId=").append(getProviderId())
.append(" }");
return result.toString();
@@ -647,10 +695,12 @@
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeInt(mConnectionState);
dest.writeString(mClientPackageName);
+ dest.writeString(mPackageName);
dest.writeInt(mVolumeHandling);
dest.writeInt(mVolumeMax);
dest.writeInt(mVolume);
dest.writeString(mAddress);
+ dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()]));
dest.writeBundle(mExtras);
dest.writeString(mProviderId);
}
@@ -671,10 +721,12 @@
@ConnectionState
int mConnectionState;
String mClientPackageName;
+ String mPackageName;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
int mVolumeMax;
int mVolume;
String mAddress;
+ Set<String> mDeduplicationIds;
Bundle mExtras;
String mProviderId;
@@ -698,6 +750,7 @@
mId = id;
mName = name;
mFeatures = new ArrayList<>();
+ mDeduplicationIds = Set.of();
}
/**
@@ -733,10 +786,12 @@
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
mClientPackageName = routeInfo.mClientPackageName;
+ mPackageName = routeInfo.mPackageName;
mVolumeHandling = routeInfo.mVolumeHandling;
mVolumeMax = routeInfo.mVolumeMax;
mVolume = routeInfo.mVolume;
mAddress = routeInfo.mAddress;
+ mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds);
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
@@ -860,6 +915,16 @@
}
/**
+ * Sets the package name of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setPackageName(@NonNull String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /**
* Sets the route's volume handling.
*/
@NonNull
@@ -897,6 +962,20 @@
}
/**
+ * Sets the deduplication ID of the route.
+ * Routes have the same ID could be removed even when
+ * they are from different providers.
+ * <p>
+ * If it's {@code null}, the route will not be removed.
+ * @see RouteDiscoveryPreference#shouldRemoveDuplicates()
+ */
+ @NonNull
+ public Builder setDeduplicationIds(@NonNull Set<String> id) {
+ mDeduplicationIds = Set.copyOf(id);
+ return this;
+ }
+
+ /**
* Sets a bundle of extras for the route.
* <p>
* Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 4b32dbf..b485eb5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -34,15 +34,18 @@
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -302,8 +305,7 @@
mSystemController = new SystemRoutingController(
ensureClientPackageNameForSystemSession(
sManager.getSystemRoutingSession(clientPackageName)));
- mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
- sManager.getPreferredFeatures(clientPackageName), true).build();
+ mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName);
updateAllRoutesFromManager();
// Only used by non-system MediaRouter2.
@@ -1060,11 +1062,48 @@
.build();
}
+ private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes,
+ RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ return routes;
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes);
+ // take the negative for descending order
+ sortedRoutes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return sortedRoutes;
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
- RouteDiscoveryPreference discoveryRequest) {
- return routes.stream()
- .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
- .collect(Collectors.toList());
+ RouteDiscoveryPreference discoveryPreference) {
+
+ Set<String> deduplicationIdSet = new ArraySet<>();
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+ continue;
+ }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
+ }
+ filteredRoutes.add(route);
+ }
+ return filteredRoutes;
}
private void updateAllRoutesFromManager() {
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 83fa7c2..8635c0e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -29,6 +29,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -36,15 +38,18 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -84,7 +89,8 @@
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap =
+ new ConcurrentHashMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
@@ -247,25 +253,8 @@
*/
@NonNull
public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
- List<MediaRoute2Info> routes = new ArrayList<>();
-
- String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
- }
- }
- }
- return routes;
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
+ null);
}
/**
@@ -281,27 +270,70 @@
*/
@NonNull
public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+ return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false,
+ (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute());
+ }
+
+ private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) {
+ if (!preference.shouldRemoveDuplicates()) {
+ synchronized (mRoutesLock) {
+ return List.copyOf(mRoutes.values());
+ }
+ }
+ Map<String, Integer> packagePriority = new ArrayMap<>();
+ int count = preference.getDeduplicationPackageOrder().size();
+ for (int i = 0; i < count; i++) {
+ // the last package will have 1 as the priority
+ packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i);
+ }
+ ArrayList<MediaRoute2Info> routes;
+ synchronized (mRoutesLock) {
+ routes = new ArrayList<>(mRoutes.values());
+ }
+ // take the negative for descending order
+ routes.sort(Comparator.comparingInt(
+ r -> -packagePriority.getOrDefault(r.getPackageName(), 0)));
+ return routes;
+ }
+
+ private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo,
+ boolean includeSelectedRoutes,
+ @Nullable Predicate<MediaRoute2Info> additionalFilter) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
List<MediaRoute2Info> routes = new ArrayList<>();
+ Set<String> deduplicationIdSet = new ArraySet<>();
String packageName = sessionInfo.getClientPackageName();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : mRoutes.values()) {
- if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- routes.add(route);
+ RouteDiscoveryPreference discoveryPreference =
+ mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
+
+ for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) {
+ if (sessionInfo.getTransferableRoutes().contains(route.getId())
+ || (includeSelectedRoutes
+ && sessionInfo.getSelectedRoutes().contains(route.getId()))) {
+ routes.add(route);
+ continue;
+ }
+ if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
+ || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+ continue;
+ }
+ if (!discoveryPreference.getAllowedPackages().isEmpty()
+ && !discoveryPreference.getAllowedPackages()
+ .contains(route.getPackageName())) {
+ continue;
+ }
+ if (additionalFilter != null && !additionalFilter.test(route)) {
+ continue;
+ }
+ if (discoveryPreference.shouldRemoveDuplicates()) {
+ if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
continue;
}
- // Add Phone -> Cast and Cast -> Phone
- if (route.hasAnyFeatures(preferredFeatures)
- && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) {
- routes.add(route);
- }
+ deduplicationIdSet.addAll(route.getDeduplicationIds());
}
+ routes.add(route);
}
return routes;
}
@@ -310,44 +342,10 @@
* Returns the preferred features of the specified package name.
*/
@NonNull
- public List<String> getPreferredFeatures(@NonNull String packageName) {
+ public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
- return preferredFeatures;
- }
-
- /**
- * Returns a list of routes which are related to the given package name in the given route list.
- */
- @NonNull
- public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes,
- @NonNull String packageName) {
- Objects.requireNonNull(routes, "routes must not be null");
- Objects.requireNonNull(packageName, "packageName must not be null");
-
- List<RoutingSessionInfo> sessions = getRoutingSessions(packageName);
- RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1);
-
- List<MediaRoute2Info> result = new ArrayList<>();
- List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
- if (preferredFeatures == null) {
- preferredFeatures = Collections.emptyList();
- }
-
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- if (route.hasAnyFeatures(preferredFeatures)
- || sessionInfo.getSelectedRoutes().contains(route.getId())
- || sessionInfo.getTransferableRoutes().contains(route.getId())) {
- result.add(route);
- }
- }
- }
- return result;
+ return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY);
}
/**
@@ -713,19 +711,19 @@
}
}
- void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
- if (preferredFeatures == null) {
- mPreferredFeaturesMap.remove(packageName);
+ void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) {
+ if (preference == null) {
+ mDiscoveryPreferenceMap.remove(packageName);
return;
}
- List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
- if ((prevFeatures == null && preferredFeatures.size() == 0)
- || Objects.equals(preferredFeatures, prevFeatures)) {
+ RouteDiscoveryPreference prevPreference =
+ mDiscoveryPreferenceMap.put(packageName, preference);
+ if (Objects.equals(preference, prevPreference)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(() -> record.mCallback
- .onPreferredFeaturesChanged(packageName, preferredFeatures));
+ .onDiscoveryPreferenceChanged(packageName, preference));
}
}
@@ -1047,6 +1045,17 @@
@NonNull List<String> preferredFeatures) {}
/**
+ * Called when the preferred route features of an app is changed.
+ *
+ * @param packageName the package name of the application
+ * @param discoveryPreference the new discovery preference set by the application.
+ */
+ default void onDiscoveryPreferenceChanged(@NonNull String packageName,
+ @NonNull RouteDiscoveryPreference discoveryPreference) {
+ onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures());
+ }
+
+ /**
* Called when a previous request has failed.
*
* @param reason the reason that the request has failed. Can be one of followings:
@@ -1125,9 +1134,10 @@
}
@Override
- public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
- MediaRouter2Manager.this, packageName, features));
+ public void notifyDiscoveryPreferenceChanged(String packageName,
+ RouteDiscoveryPreference discoveryPreference) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference,
+ MediaRouter2Manager.this, packageName, discoveryPreference));
}
@Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 37fee84..0045018 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -64,6 +64,13 @@
@NonNull
private final List<String> mPreferredFeatures;
+ @NonNull
+ private final List<String> mRequiredFeatures;
+ @NonNull
+ private final List<String> mPackagesOrder;
+ @NonNull
+ private final List<String> mAllowedPackages;
+
private final boolean mShouldPerformActiveScan;
@Nullable
private final Bundle mExtras;
@@ -78,12 +85,18 @@
RouteDiscoveryPreference(@NonNull Builder builder) {
mPreferredFeatures = builder.mPreferredFeatures;
+ mRequiredFeatures = builder.mRequiredFeatures;
+ mPackagesOrder = builder.mPackageOrder;
+ mAllowedPackages = builder.mAllowedPackages;
mShouldPerformActiveScan = builder.mActiveScan;
mExtras = builder.mExtras;
}
RouteDiscoveryPreference(@NonNull Parcel in) {
mPreferredFeatures = in.createStringArrayList();
+ mRequiredFeatures = in.createStringArrayList();
+ mPackagesOrder = in.createStringArrayList();
+ mAllowedPackages = in.createStringArrayList();
mShouldPerformActiveScan = in.readBoolean();
mExtras = in.readBundle();
}
@@ -96,6 +109,8 @@
* {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
* or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
* </p>
+ *
+ * @see #getRequiredFeatures()
*/
@NonNull
public List<String> getPreferredFeatures() {
@@ -103,6 +118,47 @@
}
/**
+ * Gets the required features of routes that media router would like to discover.
+ * <p>
+ * Routes that have all the required features will be discovered.
+ * They may include predefined features such as
+ * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
+ * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
+ *
+ * @see #getPreferredFeatures()
+ */
+ @NonNull
+ public List<String> getRequiredFeatures() {
+ return mRequiredFeatures;
+ }
+
+ /**
+ * Gets the ordered list of package names used to remove duplicate routes.
+ * <p>
+ * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
+ * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes
+ * have a deduplication ID in common, only the route from the provider whose package name is
+ * first in the provided list will remain.
+ *
+ * @see #shouldRemoveDuplicates()
+ */
+ @NonNull
+ public List<String> getDeduplicationPackageOrder() {
+ return mPackagesOrder;
+ }
+
+ /**
+ * Gets the list of allowed packages.
+ * <p>
+ * If it's not empty, it will only discover routes from the provider whose package name
+ * belongs to the list.
+ */
+ @NonNull
+ public List<String> getAllowedPackages() {
+ return mAllowedPackages;
+ }
+
+ /**
* Gets whether active scanning should be performed.
* <p>
* If any of discovery preferences sets this as {@code true}, active scanning will
@@ -114,6 +170,15 @@
}
/**
+ * Gets whether duplicate routes removal is enabled.
+ *
+ * @see #getDeduplicationPackageOrder()
+ */
+ public boolean shouldRemoveDuplicates() {
+ return !mPackagesOrder.isEmpty();
+ }
+
+ /**
* @hide
*/
public Bundle getExtras() {
@@ -128,6 +193,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStringList(mPreferredFeatures);
+ dest.writeStringList(mRequiredFeatures);
+ dest.writeStringList(mPackagesOrder);
+ dest.writeStringList(mAllowedPackages);
dest.writeBoolean(mShouldPerformActiveScan);
dest.writeBundle(mExtras);
}
@@ -155,14 +223,17 @@
return false;
}
RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
- //TODO: Make this order-free
return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
+ && Objects.equals(mPackagesOrder, other.mPackagesOrder)
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages)
&& mShouldPerformActiveScan == other.mShouldPerformActiveScan;
}
@Override
public int hashCode() {
- return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan);
+ return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages,
+ mShouldPerformActiveScan);
}
/**
@@ -170,13 +241,21 @@
*/
public static final class Builder {
List<String> mPreferredFeatures;
+ List<String> mRequiredFeatures;
+ List<String> mPackageOrder;
+ List<String> mAllowedPackages;
+
boolean mActiveScan;
+
Bundle mExtras;
public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
.collect(Collectors.toList());
+ mRequiredFeatures = List.of();
+ mPackageOrder = List.of();
+ mAllowedPackages = List.of();
mActiveScan = activeScan;
}
@@ -184,12 +263,15 @@
Objects.requireNonNull(preference, "preference must not be null");
mPreferredFeatures = preference.getPreferredFeatures();
+ mRequiredFeatures = preference.getRequiredFeatures();
+ mPackageOrder = preference.getDeduplicationPackageOrder();
+ mAllowedPackages = preference.getAllowedPackages();
mActiveScan = preference.shouldPerformActiveScan();
mExtras = preference.getExtras();
}
/**
- * A constructor to combine all of the preferences into a single preference.
+ * A constructor to combine all the preferences into a single preference.
* It ignores extras of preferences.
*
* @hide
@@ -224,6 +306,30 @@
}
/**
+ * Sets the required route features to discover.
+ */
+ @NonNull
+ public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
+ Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
+ mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ /**
+ * Sets the list of package names of providers that media router would like to discover.
+ * <p>
+ * If it's non-empty, media router only discovers route from the provider in the list.
+ * The default value is empty, which discovers routes from all providers.
+ */
+ @NonNull
+ public Builder setAllowedPackages(@NonNull List<String> allowedPackages) {
+ Objects.requireNonNull(allowedPackages, "allowedPackages must not be null");
+ mAllowedPackages = List.copyOf(allowedPackages);
+ return this;
+ }
+
+ /**
* Sets if active scanning should be performed.
* <p>
* Since active scanning uses more system resources, set this as {@code true} only
@@ -237,6 +343,24 @@
}
/**
+ * Sets the order of packages to use when removing duplicate routes.
+ * <p>
+ * Routes are deduplicated based on their
+ * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}.
+ * If two routes have a deduplication ID in common, only the route from the provider whose
+ * package name is first in the provided list will remain.
+ *
+ * @param packageOrder ordered list of package names used to remove duplicate routes, or an
+ * empty list if deduplication should not be enabled.
+ */
+ @NonNull
+ public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) {
+ Objects.requireNonNull(packageOrder, "packageOrder must not be null");
+ mPackageOrder = List.copyOf(packageOrder);
+ return this;
+ }
+
+ /**
* Sets the extras of the route.
* @hide
*/
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 1053fb7..f17189d 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -17,12 +17,17 @@
package android.media.audio.common;
import android.annotation.NonNull;
+import android.media.AudioDescriptor;
+import android.media.AudioDeviceAttributes;
import android.media.AudioFormat;
+import android.media.AudioSystem;
import android.media.MediaFormat;
import android.os.Parcel;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.stream.Collectors;
+
/**
* This class provides utility functions for converting between
* the AIDL types defined in 'android.media.audio.common' and:
@@ -525,6 +530,351 @@
}
}
+ /**
+ * Convert from SDK AudioDeviceAttributes to AIDL AudioPort.
+ */
+ public static AudioPort api2aidl_AudioDeviceAttributes_AudioPort(
+ @NonNull AudioDeviceAttributes attributes) {
+ AudioPort port = new AudioPort();
+ port.name = attributes.getName();
+ // TO DO: b/211611504 Convert attributes.getAudioProfiles() to AIDL as well.
+ port.profiles = new AudioProfile[]{};
+ port.extraAudioDescriptors = attributes.getAudioDescriptors().stream()
+ .map(descriptor -> api2aidl_AudioDescriptor_ExtraAudioDescriptor(descriptor))
+ .collect(Collectors.toList()).toArray(ExtraAudioDescriptor[]::new);
+ port.flags = new AudioIoFlags();
+ port.gains = new AudioGain[]{};
+ AudioPortDeviceExt deviceExt = new AudioPortDeviceExt();
+ deviceExt.device = new AudioDevice();
+ deviceExt.encodedFormats = new AudioFormatDescription[]{};
+ deviceExt.device.type =
+ api2aidl_NativeType_AudioDeviceDescription(attributes.getInternalType());
+ deviceExt.device.address = AudioDeviceAddress.id(attributes.getAddress());
+ port.ext = AudioPortExt.device(deviceExt);
+ return port;
+ }
+
+ /**
+ * Convert from SDK AudioDescriptor to AIDL ExtraAudioDescriptor.
+ */
+ public static ExtraAudioDescriptor api2aidl_AudioDescriptor_ExtraAudioDescriptor(
+ @NonNull AudioDescriptor descriptor) {
+ ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor();
+ extraDescriptor.standard =
+ api2aidl_AudioDescriptorStandard_AudioStandard(descriptor.getStandard());
+ extraDescriptor.audioDescriptor = descriptor.getDescriptor();
+ extraDescriptor.encapsulationType =
+ api2aidl_AudioProfileEncapsulationType_AudioEncapsulationType(
+ descriptor.getEncapsulationType());
+ return extraDescriptor;
+ }
+
+ /**
+ * Convert from SDK AudioDescriptor to AIDL ExtraAudioDescriptor.
+ */
+ public static @NonNull AudioDescriptor aidl2api_ExtraAudioDescriptor_AudioDescriptor(
+ @NonNull ExtraAudioDescriptor extraDescriptor) {
+ AudioDescriptor descriptor = new AudioDescriptor(
+ aidl2api_AudioStandard_AudioDescriptorStandard(extraDescriptor.standard),
+ aidl2api_AudioEncapsulationType_AudioProfileEncapsulationType(
+ extraDescriptor.encapsulationType),
+ extraDescriptor.audioDescriptor);
+ return descriptor;
+ }
+
+ /**
+ * Convert from SDK AudioDescriptor#mStandard to AIDL AudioStandard
+ */
+ @AudioStandard
+ public static int api2aidl_AudioDescriptorStandard_AudioStandard(
+ @AudioDescriptor.AudioDescriptorStandard int standard) {
+ switch (standard) {
+ case AudioDescriptor.STANDARD_EDID:
+ return AudioStandard.EDID;
+ case AudioDescriptor.STANDARD_NONE:
+ default:
+ return AudioStandard.NONE;
+ }
+ }
+
+ /**
+ * Convert from AIDL AudioStandard to SDK AudioDescriptor#mStandard
+ */
+ @AudioDescriptor.AudioDescriptorStandard
+ public static int aidl2api_AudioStandard_AudioDescriptorStandard(@AudioStandard int standard) {
+ switch (standard) {
+ case AudioStandard.EDID:
+ return AudioDescriptor.STANDARD_EDID;
+ case AudioStandard.NONE:
+ default:
+ return AudioDescriptor.STANDARD_NONE;
+ }
+ }
+
+ /**
+ * Convert from SDK AudioProfile.EncapsulationType to AIDL AudioEncapsulationType
+ */
+ @AudioEncapsulationType
+ public static int api2aidl_AudioProfileEncapsulationType_AudioEncapsulationType(
+ @android.media.AudioProfile.EncapsulationType int type) {
+ switch (type) {
+ case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937:
+ return AudioEncapsulationType.IEC61937;
+ case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE:
+ default:
+ return AudioEncapsulationType.NONE;
+ }
+ }
+
+ /**
+ * Convert from AIDL AudioEncapsulationType to SDK AudioProfile.EncapsulationType
+ */
+ @android.media.AudioProfile.EncapsulationType
+ public static int aidl2api_AudioEncapsulationType_AudioProfileEncapsulationType(
+ @AudioEncapsulationType int type) {
+ switch (type) {
+ case AudioEncapsulationType.IEC61937:
+ return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937;
+ case AudioEncapsulationType.NONE:
+ default:
+ return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
+ }
+ }
+
+ /**
+ * Convert from SDK native type to AIDL AudioDeviceDescription
+ */
+ public static AudioDeviceDescription api2aidl_NativeType_AudioDeviceDescription(
+ int nativeType) {
+ AudioDeviceDescription aidl = new AudioDeviceDescription();
+ aidl.connection = "";
+ switch (nativeType) {
+ case AudioSystem.DEVICE_OUT_EARPIECE:
+ aidl.type = AudioDeviceType.OUT_SPEAKER_EARPIECE;
+ break;
+ case AudioSystem.DEVICE_OUT_SPEAKER:
+ aidl.type = AudioDeviceType.OUT_SPEAKER;
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+ aidl.type = AudioDeviceType.OUT_HEADPHONE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT:
+ aidl.type = AudioDeviceType.OUT_CARKIT;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
+ aidl.type = AudioDeviceType.OUT_HEADPHONE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER:
+ aidl.type = AudioDeviceType.OUT_SPEAKER;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP;
+ break;
+ case AudioSystem.DEVICE_OUT_TELEPHONY_TX:
+ aidl.type = AudioDeviceType.OUT_TELEPHONY_TX;
+ break;
+ case AudioSystem.DEVICE_OUT_AUX_LINE:
+ aidl.type = AudioDeviceType.OUT_LINE_AUX;
+ break;
+ case AudioSystem.DEVICE_OUT_SPEAKER_SAFE:
+ aidl.type = AudioDeviceType.OUT_SPEAKER_SAFE;
+ break;
+ case AudioSystem.DEVICE_OUT_HEARING_AID:
+ aidl.type = AudioDeviceType.OUT_HEARING_AID;
+ aidl.connection = AudioDeviceDescription.CONNECTION_WIRELESS;
+ break;
+ case AudioSystem.DEVICE_OUT_ECHO_CANCELLER:
+ aidl.type = AudioDeviceType.OUT_ECHO_CANCELLER;
+ break;
+ case AudioSystem.DEVICE_OUT_BLE_SPEAKER:
+ aidl.type = AudioDeviceType.OUT_SPEAKER;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
+ break;
+ case AudioSystem.DEVICE_IN_BUILTIN_MIC:
+ aidl.type = AudioDeviceType.IN_MICROPHONE;
+ break;
+ case AudioSystem.DEVICE_IN_BACK_MIC:
+ aidl.type = AudioDeviceType.IN_MICROPHONE_BACK;
+ break;
+ case AudioSystem.DEVICE_IN_TELEPHONY_RX:
+ aidl.type = AudioDeviceType.IN_TELEPHONY_RX;
+ break;
+ case AudioSystem.DEVICE_IN_TV_TUNER:
+ aidl.type = AudioDeviceType.IN_TV_TUNER;
+ break;
+ case AudioSystem.DEVICE_IN_LOOPBACK:
+ aidl.type = AudioDeviceType.IN_LOOPBACK;
+ break;
+ case AudioSystem.DEVICE_IN_BLUETOOTH_BLE:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
+ break;
+ case AudioSystem.DEVICE_IN_ECHO_REFERENCE:
+ aidl.type = AudioDeviceType.IN_ECHO_REFERENCE;
+ break;
+ case AudioSystem.DEVICE_IN_WIRED_HEADSET:
+ aidl.type = AudioDeviceType.IN_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+ aidl.type = AudioDeviceType.OUT_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET:
+ aidl.type = AudioDeviceType.IN_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET:
+ aidl.type = AudioDeviceType.OUT_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_SCO;
+ break;
+ case AudioSystem.DEVICE_IN_HDMI:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI;
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI;
+ break;
+ case AudioSystem.DEVICE_IN_REMOTE_SUBMIX:
+ aidl.type = AudioDeviceType.IN_SUBMIX;
+ break;
+ case AudioSystem.DEVICE_OUT_REMOTE_SUBMIX:
+ aidl.type = AudioDeviceType.OUT_SUBMIX;
+ break;
+ case AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET:
+ aidl.type = AudioDeviceType.IN_DOCK;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET:
+ aidl.type = AudioDeviceType.OUT_DOCK;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET:
+ aidl.type = AudioDeviceType.IN_DOCK;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET:
+ aidl.type = AudioDeviceType.OUT_DOCK;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_IN_USB_ACCESSORY:
+ aidl.type = AudioDeviceType.IN_ACCESSORY;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_OUT_USB_ACCESSORY:
+ aidl.type = AudioDeviceType.OUT_ACCESSORY;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_IN_USB_DEVICE:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_OUT_USB_DEVICE:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_IN_FM_TUNER:
+ aidl.type = AudioDeviceType.IN_FM_TUNER;
+ break;
+ case AudioSystem.DEVICE_OUT_FM:
+ aidl.type = AudioDeviceType.OUT_FM;
+ break;
+ case AudioSystem.DEVICE_IN_LINE:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_OUT_LINE:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_ANALOG;
+ break;
+ case AudioSystem.DEVICE_IN_SPDIF:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_SPDIF;
+ break;
+ case AudioSystem.DEVICE_OUT_SPDIF:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_SPDIF;
+ break;
+ case AudioSystem.DEVICE_IN_BLUETOOTH_A2DP:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP;
+ break;
+ case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_A2DP;
+ break;
+ case AudioSystem.DEVICE_IN_IP:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_IP_V4;
+ break;
+ case AudioSystem.DEVICE_OUT_IP:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_IP_V4;
+ break;
+ case AudioSystem.DEVICE_IN_BUS:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BUS;
+ break;
+ case AudioSystem.DEVICE_OUT_BUS:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BUS;
+ break;
+ case AudioSystem.DEVICE_IN_PROXY:
+ aidl.type = AudioDeviceType.IN_AFE_PROXY;
+ break;
+ case AudioSystem.DEVICE_OUT_PROXY:
+ aidl.type = AudioDeviceType.OUT_AFE_PROXY;
+ break;
+ case AudioSystem.DEVICE_IN_USB_HEADSET:
+ aidl.type = AudioDeviceType.IN_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_OUT_USB_HEADSET:
+ aidl.type = AudioDeviceType.OUT_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_USB;
+ break;
+ case AudioSystem.DEVICE_IN_HDMI_ARC:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_ARC;
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI_ARC:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_ARC;
+ break;
+ case AudioSystem.DEVICE_IN_HDMI_EARC:
+ aidl.type = AudioDeviceType.IN_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_EARC;
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI_EARC:
+ aidl.type = AudioDeviceType.OUT_DEVICE;
+ aidl.connection = AudioDeviceDescription.CONNECTION_HDMI_EARC;
+ break;
+ case AudioSystem.DEVICE_IN_BLE_HEADSET:
+ aidl.type = AudioDeviceType.IN_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
+ break;
+ case AudioSystem.DEVICE_OUT_BLE_HEADSET:
+ aidl.type = AudioDeviceType.OUT_HEADSET;
+ aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
+ break;
+ case AudioSystem.DEVICE_IN_DEFAULT:
+ aidl.type = AudioDeviceType.IN_DEFAULT;
+ break;
+ case AudioSystem.DEVICE_OUT_DEFAULT:
+ aidl.type = AudioDeviceType.OUT_DEFAULT;
+ break;
+ default:
+ aidl.type = AudioDeviceType.NONE;
+ }
+ return aidl;
+ }
+
private static native int aidl2legacy_AudioChannelLayout_Parcel_audio_channel_mask_t(
Parcel aidl, boolean isInput);
private static native Parcel legacy2aidl_audio_channel_mask_t_AudioChannelLayout_Parcel(
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..e14e879 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..75236f4 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 */
@@ -1859,6 +1851,7 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public int getClientPid(@NonNull String sessionId) {
return getClientPidInternal(sessionId);
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/AppLinkInfo.aidl b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
index 7c52d01..6759fc4 100644
--- a/media/java/android/media/tv/interactive/AppLinkInfo.aidl
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
@@ -16,4 +16,4 @@
package android.media.tv.interactive;
-parcelable AppLinkInfo;
\ No newline at end of file
+parcelable AppLinkInfo;
diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java
index 5cce443..cd201f7 100644
--- a/media/java/android/media/tv/interactive/AppLinkInfo.java
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.java
@@ -23,7 +23,6 @@
/**
* App link information used by TV interactive app to launch Android apps.
- * @hide
*/
public final class AppLinkInfo implements Parcelable {
private @NonNull String mPackageName;
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 4a99715..f75aa56 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -192,7 +192,7 @@
*
* @see #sendAppLinkCommand(String, Bundle)
*/
- public static final String KEY_PACKAGE_NAME = "package_name";
+ public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
/**
* Key for class name in app link.
@@ -200,7 +200,7 @@
*
* @see #sendAppLinkCommand(String, Bundle)
*/
- public static final String KEY_CLASS_NAME = "class_name";
+ public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
/**
* Key for command type in app link command.
@@ -208,7 +208,7 @@
*
* @see #sendAppLinkCommand(String, Bundle)
*/
- public static final String KEY_COMMAND_TYPE = "command_type";
+ public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
/**
* Key for service ID in app link command.
@@ -216,7 +216,7 @@
*
* @see #sendAppLinkCommand(String, Bundle)
*/
- public static final String KEY_SERVICE_ID = "service_id";
+ public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
/**
* Key for back URI in app link command.
@@ -224,7 +224,7 @@
*
* @see #sendAppLinkCommand(String, Bundle)
*/
- public static final String KEY_BACK_URI = "back_uri";
+ public static final String APP_LINK_KEY_BACK_URI = "back_uri";
/**
* Broadcast intent action to send app command to TV app.
@@ -761,7 +761,6 @@
/**
* Registers app link info.
- * @hide
*/
public void registerAppLinkInfo(
@NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
@@ -774,7 +773,6 @@
/**
* Unregisters app link info.
- * @hide
*/
public void unregisterAppLinkInfo(
@NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index c9856bf..57730ac 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -245,14 +245,12 @@
/**
* Registers App link info.
- * @hide
*/
public void onRegisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
}
/**
* Unregisters App link info.
- * @hide
*/
public void onUnregisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
}
@@ -558,7 +556,6 @@
/**
* Called when a broadcast info response is received.
- * @hide
*/
public void onBroadcastInfoResponse(@NonNull BroadcastInfoResponse response) {
}
@@ -661,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() {
@@ -686,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/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index 0f5bf08..14a9144 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -36,13 +36,8 @@
public class DemuxCapabilities {
/** @hide */
- @IntDef(flag = true, value = {
- Filter.TYPE_TS,
- Filter.TYPE_MMTP,
- Filter.TYPE_IP,
- Filter.TYPE_TLV,
- Filter.TYPE_ALP
- })
+ @IntDef(value = {Filter.TYPE_TS, Filter.TYPE_MMTP, Filter.TYPE_IP, Filter.TYPE_TLV,
+ Filter.TYPE_ALP})
@Retention(RetentionPolicy.SOURCE)
public @interface FilterCapabilities {}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index be114d4..1e32cad 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -68,6 +68,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -1565,9 +1566,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
@@ -1602,25 +1603,27 @@
*
* @param statusTypes an array of status types.
*
- * @return an array of current readiness states. {@code null} if the operation failed or
- * unsupported versions.
+ * @return a list of current readiness states. It is empty if the operation fails or unsupported
+ * versions.
* @throws IllegalStateException if there is no active frontend currently.
*/
- @Nullable
- @SuppressLint("ArrayReturn")
- @SuppressWarnings("NullableCollection")
- public FrontendStatusReadiness[] getFrontendStatusReadiness(
+ @NonNull
+ public List<FrontendStatusReadiness> getFrontendStatusReadiness(
@NonNull @FrontendStatusType int[] statusTypes) {
mFrontendLock.lock();
try {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
- return null;
+ TunerVersionChecker.TUNER_VERSION_2_0, "Get fronted status readiness")) {
+ return Collections.EMPTY_LIST;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
- return nativeGetFrontendStatusReadiness(statusTypes);
+ FrontendStatusReadiness[] readiness = nativeGetFrontendStatusReadiness(statusTypes);
+ if (readiness == null) {
+ return Collections.EMPTY_LIST;
+ }
+ return Arrays.asList(readiness);
} finally {
mFrontendLock.unlock();
}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 9f44236..14accaa 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -153,8 +153,8 @@
/** @hide */
- @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER,
- STATUS_HIGH_WATER, STATUS_OVERFLOW})
+ @IntDef(prefix = "STATUS_",
+ value = {STATUS_DATA_READY, STATUS_LOW_WATER, STATUS_HIGH_WATER, STATUS_OVERFLOW})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {}
@@ -185,8 +185,7 @@
public static final int STATUS_OVERFLOW = DemuxFilterStatus.OVERFLOW;
/** @hide */
- @IntDef(flag = true,
- prefix = "SCRAMBLING_STATUS_",
+ @IntDef(prefix = "SCRAMBLING_STATUS_",
value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
SCRAMBLING_STATUS_SCRAMBLED})
@Retention(RetentionPolicy.SOURCE)
@@ -209,8 +208,7 @@
android.hardware.tv.tuner.ScramblingStatus.SCRAMBLED;
/** @hide */
- @IntDef(flag = true,
- prefix = "MONITOR_EVENT_",
+ @IntDef(prefix = "MONITOR_EVENT_",
value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE})
@Retention(RetentionPolicy.SOURCE)
public @interface MonitorEventMask {}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index d34581d..b16d9fb 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -40,8 +40,7 @@
*
* @hide
*/
- @IntDef(flag = true,
- value = {TS_INDEX_INVALID, TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+ @IntDef(value = {TS_INDEX_INVALID, TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR,
@@ -165,7 +164,6 @@
* @hide
*/
@IntDef(prefix = "SC_INDEX_",
- flag = true,
value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
SC_INDEX_SEQUENCE, SC_INDEX_I_SLICE, SC_INDEX_P_SLICE,
SC_INDEX_B_SLICE, SC_INDEX_SI_SLICE, SC_INDEX_SP_SLICE})
@@ -214,8 +212,7 @@
*
* @hide
*/
- @IntDef(flag = true,
- value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ @IntDef(value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
SC_HEVC_INDEX_SLICE_TRAIL_CRA})
@@ -258,8 +255,7 @@
/**
* @hide
*/
- @IntDef(flag = true,
- prefix = "SC_",
+ @IntDef(prefix = "SC_",
value = {
SC_INDEX_I_FRAME,
SC_INDEX_P_FRAME,
diff --git a/media/java/android/media/tv/tuner/filter/SharedFilter.java b/media/java/android/media/tv/tuner/filter/SharedFilter.java
index 740ab9c..21964ee 100644
--- a/media/java/android/media/tv/tuner/filter/SharedFilter.java
+++ b/media/java/android/media/tv/tuner/filter/SharedFilter.java
@@ -38,7 +38,7 @@
@SystemApi
public final class SharedFilter implements AutoCloseable {
/** @hide */
- @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_INACCESSIBLE})
+ @IntDef(prefix = "STATUS_", value = {STATUS_INACCESSIBLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index e0405ef..6c1134a 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -36,8 +36,7 @@
@SystemApi
public class AnalogFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "SIGNAL_TYPE_",
+ @IntDef(prefix = "SIGNAL_TYPE_",
value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_AUTO, SIGNAL_TYPE_PAL, SIGNAL_TYPE_PAL_M,
SIGNAL_TYPE_PAL_N, SIGNAL_TYPE_PAL_60, SIGNAL_TYPE_NTSC, SIGNAL_TYPE_NTSC_443,
SIGNAL_TYPE_SECAM})
@@ -82,8 +81,7 @@
public static final int SIGNAL_TYPE_SECAM = FrontendAnalogType.SECAM;
/** @hide */
- @IntDef(flag = true,
- prefix = "SIF_",
+ @IntDef(prefix = "SIF_",
value = {SIF_UNDEFINED, SIF_AUTO, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
SIF_DK1_A2, SIF_DK2_A2, SIF_DK3_A2, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
SIF_M_EIAJ, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index a7157e2..c99f911 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -39,8 +39,7 @@
public class Atsc3FrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "BANDWIDTH_",
+ @IntDef(prefix = "BANDWIDTH_",
value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ,
BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ})
@Retention(RetentionPolicy.SOURCE)
@@ -69,8 +68,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO,
MODULATION_MOD_QPSK, MODULATION_MOD_16QAM,
MODULATION_MOD_64QAM, MODULATION_MOD_256QAM,
@@ -113,8 +111,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "TIME_INTERLEAVE_MODE_",
+ @IntDef(prefix = "TIME_INTERLEAVE_MODE_",
value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI})
@Retention(RetentionPolicy.SOURCE)
@@ -140,8 +137,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "CODERATE_",
+ @IntDef(prefix = "CODERATE_",
value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15,
CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15,
CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15})
@@ -207,8 +203,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "FEC_",
+ @IntDef(prefix = "FEC_",
value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K,
FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K})
@Retention(RetentionPolicy.SOURCE)
@@ -249,8 +244,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "DEMOD_OUTPUT_FORMAT_",
+ @IntDef(prefix = "DEMOD_OUTPUT_FORMAT_",
value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index 3071ce8..64c6ce6 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -34,8 +34,7 @@
public class AtscFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB,
MODULATION_MOD_16VSB})
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java
index 6b5d6ca..07c1fbf 100644
--- a/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DtmbFrontendSettings.java
@@ -43,8 +43,7 @@
public final class DtmbFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "BANDWIDTH_",
+ @IntDef(prefix = "BANDWIDTH_",
value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_6MHZ, BANDWIDTH_8MHZ})
@Retention(RetentionPolicy.SOURCE)
public @interface Bandwidth {}
@@ -68,8 +67,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "TIME_INTERLEAVE_MODE_",
+ @IntDef(prefix = "TIME_INTERLEAVE_MODE_",
value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
TIME_INTERLEAVE_MODE_TIMER_INT_240, TIME_INTERLEAVE_MODE_TIMER_INT_720})
@Retention(RetentionPolicy.SOURCE)
@@ -97,8 +95,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "GUARD_INTERVAL_",
+ @IntDef(prefix = "GUARD_INTERVAL_",
value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO,
GUARD_INTERVAL_PN_420_VARIOUS, GUARD_INTERVAL_PN_595_CONST,
GUARD_INTERVAL_PN_945_VARIOUS, GUARD_INTERVAL_PN_420_CONST,
@@ -143,8 +140,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_CONSTELLATION_UNDEFINED, MODULATION_CONSTELLATION_AUTO,
MODULATION_CONSTELLATION_4QAM, MODULATION_CONSTELLATION_4QAM_NR,
MODULATION_CONSTELLATION_16QAM, MODULATION_CONSTELLATION_32QAM,
@@ -187,8 +183,7 @@
FrontendDtmbModulation.CONSTELLATION_64QAM;
/** @hide */
- @IntDef(flag = true,
- prefix = "CODERATE_",
+ @IntDef(prefix = "CODERATE_",
value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_5, CODERATE_3_5, CODERATE_4_5})
@Retention(RetentionPolicy.SOURCE)
public @interface CodeRate {}
@@ -215,8 +210,7 @@
public static final int CODERATE_4_5 = FrontendDtmbCodeRate.CODERATE_4_5;
/** @hide */
- @IntDef(flag = true,
- prefix = "TRANSMISSION_MODE_",
+ @IntDef(prefix = "TRANSMISSION_MODE_",
value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO,
TRANSMISSION_MODE_C1, TRANSMISSION_MODE_C3780})
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index afe953d..45bfc09 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -40,8 +40,7 @@
public class DvbcFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM,
MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM,
MODULATION_MOD_256QAM})
@@ -98,8 +97,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "ANNEX_",
+ @IntDef(prefix = "ANNEX_",
value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C})
@Retention(RetentionPolicy.SOURCE)
public @interface Annex {}
@@ -159,8 +157,7 @@
android.hardware.tv.tuner.FrontendSpectralInversion.INVERTED;
/** @hide */
- @IntDef(flag = true,
- prefix = "TIME_INTERLEAVE_MODE_",
+ @IntDef(prefix = "TIME_INTERLEAVE_MODE_",
value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
TIME_INTERLEAVE_MODE_128_1_0, TIME_INTERLEAVE_MODE_128_1_1,
TIME_INTERLEAVE_MODE_64_2, TIME_INTERLEAVE_MODE_32_4,
@@ -226,8 +223,7 @@
FrontendCableTimeInterleaveMode.INTERLEAVING_128_4;
/** @hide */
- @IntDef(flag = true,
- prefix = "BANDWIDTH_",
+ @IntDef(prefix = "BANDWIDTH_",
value = {BANDWIDTH_UNDEFINED, BANDWIDTH_5MHZ, BANDWIDTH_6MHZ, BANDWIDTH_7MHZ,
BANDWIDTH_8MHZ})
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index e16f192..56dbb48 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -42,8 +42,7 @@
@SystemApi
public class DvbsFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "SCAN_TYPE_",
+ @IntDef(prefix = "SCAN_TYPE_",
value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_DIRECT, SCAN_TYPE_DISEQC,
SCAN_TYPE_UNICABLE, SCAN_TYPE_JESS})
@Retention(RetentionPolicy.SOURCE)
@@ -75,8 +74,7 @@
public static final int SCAN_TYPE_JESS = FrontendDvbsScanType.JESS;
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK,
MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK,
MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK,
@@ -207,8 +205,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "STANDARD_",
+ @IntDef(prefix = "STANDARD_",
value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X})
@Retention(RetentionPolicy.SOURCE)
public @interface Standard {}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index d86e9a8..06547e1 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -42,8 +42,7 @@
public class DvbtFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "TRANSMISSION_MODE_",
+ @IntDef(prefix = "TRANSMISSION_MODE_",
value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO,
TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K,
TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K})
@@ -98,8 +97,7 @@
FrontendDvbtTransmissionMode.MODE_32K_E;
/** @hide */
- @IntDef(flag = true,
- prefix = "BANDWIDTH_",
+ @IntDef(prefix = "BANDWIDTH_",
value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ})
@Retention(RetentionPolicy.SOURCE)
@@ -140,8 +138,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "CONSTELLATION_",
+ @IntDef(prefix = "CONSTELLATION_",
value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_QPSK,
CONSTELLATION_16QAM, CONSTELLATION_64QAM, CONSTELLATION_256QAM,
CONSTELLATION_QPSK_R, CONSTELLATION_16QAM_R, CONSTELLATION_64QAM_R,
@@ -192,8 +189,7 @@
FrontendDvbtConstellation.CONSTELLATION_256QAM_R;
/** @hide */
- @IntDef(flag = true,
- prefix = "HIERARCHY_",
+ @IntDef(prefix = "HIERARCHY_",
value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
@@ -243,8 +239,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "CODERATE_",
+ @IntDef(prefix = "CODERATE_",
value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9})
@Retention(RetentionPolicy.SOURCE)
@@ -296,8 +291,7 @@
public static final int CODERATE_8_9 = FrontendDvbtCoderate.CODERATE_8_9;
/** @hide */
- @IntDef(flag = true,
- prefix = "GUARD_INTERVAL_",
+ @IntDef(prefix = "GUARD_INTERVAL_",
value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO,
GUARD_INTERVAL_1_32, GUARD_INTERVAL_1_16,
GUARD_INTERVAL_1_8, GUARD_INTERVAL_1_4,
@@ -346,8 +340,7 @@
public static final int GUARD_INTERVAL_19_256 = FrontendDvbtGuardInterval.INTERVAL_19_256;
/** @hide */
- @IntDef(flag = true,
- prefix = "STANDARD",
+ @IntDef(prefix = "STANDARD_",
value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2}
)
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 38bffec..2f45a70 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -89,8 +89,7 @@
/** @hide */
- @LongDef(flag = true,
- prefix = "FEC_",
+ @LongDef(prefix = "FEC_",
value = {FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5,
FEC_2_9, FEC_3_4, FEC_3_5, FEC_4_5, FEC_4_15, FEC_5_6, FEC_5_9, FEC_6_7, FEC_7_8,
FEC_7_9, FEC_7_15, FEC_8_9, FEC_8_15, FEC_9_10, FEC_9_20, FEC_11_15, FEC_11_20,
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index c1e9b38..9fbea72 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -25,6 +25,9 @@
import android.media.tv.tuner.TunerVersionChecker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* A Frontend Status class that contains the metrics of the active frontend.
@@ -1086,22 +1089,26 @@
}
/**
- * Gets an array of all PLPs information of ATSC3 frontend, which includes both tuned and not
+ * Gets a list of all PLPs information of ATSC3 frontend, which includes both tuned and not
* tuned PLPs for currently watching service.
*
- * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
- * doesn't return all PLPs information will throw IllegalStateException. Use
- * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+ * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version will throw
+ * UnsupportedOperationException. Use {@link TunerVersionChecker#getTunerVersion()} to check
+ * the version.
+ *
+ * @return a list of all PLPs information. It is empty if HAL doesn't return all PLPs
+ * information status.
*/
- @SuppressLint("ArrayReturn")
@NonNull
- public Atsc3PlpInfo[] getAllAtsc3PlpInfo() {
- TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_2_0, "Atsc3PlpInfo all status");
- if (mAllPlpInfo == null) {
- throw new IllegalStateException("Atsc3PlpInfo all status is empty");
+ public List<Atsc3PlpInfo> getAllAtsc3PlpInfo() {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_2_0, "Atsc3PlpInfo all status")) {
+ throw new UnsupportedOperationException("Atsc3PlpInfo all status is empty");
}
- return mAllPlpInfo;
+ if (mAllPlpInfo == null) {
+ return Collections.EMPTY_LIST;
+ }
+ return Arrays.asList(mAllPlpInfo);
}
/**
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
index 52527b3..c30753c 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
@@ -30,7 +30,7 @@
* @hide
*/
@SystemApi
-public class FrontendStatusReadiness {
+public final class FrontendStatusReadiness {
/** @hide */
@IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE,
FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE,
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 726fe15..7e83d15 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -36,8 +36,7 @@
@SystemApi
public class Isdbs3FrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK,
MODULATION_MOD_32APSK})
@@ -75,8 +74,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true,
- prefix = "CODERATE_",
+ @IntDef(prefix = "CODERATE_",
value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2,
CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5,
CODERATE_5_6, CODERATE_7_8, CODERATE_9_10})
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 51ec5ae..5029453 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -54,8 +54,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK})
@Retention(RetentionPolicy.SOURCE)
@@ -84,8 +83,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "CODERATE_",
+ @IntDef(prefix = "CODERATE_",
value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
CODERATE_5_6, CODERATE_7_8})
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 89512a0..f08a514 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -40,8 +40,7 @@
@SystemApi
public class IsdbtFrontendSettings extends FrontendSettings {
/** @hide */
- @IntDef(flag = true,
- prefix = "MODULATION_",
+ @IntDef(prefix = "MODULATION_",
value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK,
MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM})
@Retention(RetentionPolicy.SOURCE)
@@ -74,8 +73,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "MODE_",
+ @IntDef(prefix = "MODE_",
value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {}
@@ -103,8 +101,7 @@
/** @hide */
- @IntDef(flag = true,
- prefix = "BANDWIDTH_",
+ @IntDef(prefix = "BANDWIDTH_",
value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
BANDWIDTH_6MHZ})
@Retention(RetentionPolicy.SOURCE)
@@ -132,7 +129,7 @@
public static final int BANDWIDTH_6MHZ = FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
/** @hide */
- @IntDef(flag = true, prefix = "PARTIAL_RECEPTION_FLAG_",
+ @IntDef(prefix = "PARTIAL_RECEPTION_FLAG_",
value = {PARTIAL_RECEPTION_FLAG_UNDEFINED, PARTIAL_RECEPTION_FLAG_FALSE,
PARTIAL_RECEPTION_FLAG_TRUE})
@Retention(RetentionPolicy.SOURCE)
@@ -153,7 +150,7 @@
public static final int PARTIAL_RECEPTION_FLAG_TRUE = FrontendIsdbtPartialReceptionFlag.TRUE;
/** @hide */
- @IntDef(flag = true, prefix = "TIME_INTERLEAVE_MODE_",
+ @IntDef(prefix = "TIME_INTERLEAVE_MODE_",
value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
TIME_INTERLEAVE_MODE_1_0, TIME_INTERLEAVE_MODE_1_4, TIME_INTERLEAVE_MODE_1_8,
TIME_INTERLEAVE_MODE_1_16, TIME_INTERLEAVE_MODE_2_0, TIME_INTERLEAVE_MODE_2_2,
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index d8f48c2..20d711c 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -103,6 +103,7 @@
private int mDeviceType;
private String mHostType;
private boolean mSkipThumbForHost = false;
+ private volatile boolean mHostIsWindows = false;
private MtpServer mServer;
private MtpStorageManager mManager;
@@ -358,7 +359,7 @@
}
public void addStorage(StorageVolume storage) {
- MtpStorage mtpStorage = mManager.addMtpStorage(storage);
+ MtpStorage mtpStorage = mManager.addMtpStorage(storage, () -> mHostIsWindows);
mStorageMap.put(storage.getPath(), mtpStorage);
if (mServer != null) {
mServer.addStorage(mtpStorage);
@@ -413,6 +414,7 @@
}
mHostType = "";
mSkipThumbForHost = false;
+ mHostIsWindows = false;
}
@VisibleForNative
@@ -736,10 +738,12 @@
: MtpConstants.RESPONSE_GENERAL_ERROR);
case MtpConstants.DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO:
mHostType = stringValue;
+ Log.d(TAG, "setDeviceProperty." + Integer.toHexString(property)
+ + "=" + stringValue);
if (stringValue.startsWith("Android/")) {
- Log.d(TAG, "setDeviceProperty." + Integer.toHexString(property)
- + "=" + stringValue);
mSkipThumbForHost = true;
+ } else if (stringValue.startsWith("Windows/")) {
+ mHostIsWindows = true;
}
return MtpConstants.RESPONSE_OK;
}
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 88c32a3..a3754e90 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -19,6 +19,8 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.storage.StorageVolume;
+import java.util.function.Supplier;
+
/**
* This class represents a storage unit on an MTP device.
* Used only for MTP support in USB responder mode.
@@ -33,14 +35,16 @@
private final boolean mRemovable;
private final long mMaxFileSize;
private final String mVolumeName;
+ private final Supplier<Boolean> mIsHostWindows;
- public MtpStorage(StorageVolume volume, int storageId) {
+ public MtpStorage(StorageVolume volume, int storageId, Supplier<Boolean> isHostWindows) {
mStorageId = storageId;
mPath = volume.getPath();
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
mVolumeName = volume.getMediaStoreVolumeName();
+ mIsHostWindows = isHostWindows;
}
/**
@@ -93,4 +97,13 @@
public String getVolumeName() {
return mVolumeName;
}
+
+ /**
+ * Returns true if the mtp host of this storage is Windows.
+ *
+ * @return is host Windows
+ */
+ public boolean isHostWindows() {
+ return mIsHostWindows.get();
+ }
}
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
index 0bede0d..e9426cf 100644
--- a/media/java/android/mtp/MtpStorageManager.java
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -18,7 +18,11 @@
import android.media.MediaFile;
import android.os.FileObserver;
+import android.os.SystemProperties;
import android.os.storage.StorageVolume;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -35,6 +39,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.function.Supplier;
/**
* MtpStorageManager provides functionality for listing, tracking, and notifying MtpServer of
@@ -199,7 +204,38 @@
}
public long getSize() {
- return mIsDir ? 0 : getPath().toFile().length();
+ return mIsDir ? 0 : maybeApplyTranscodeLengthWorkaround(getPath().toFile().length());
+ }
+
+ private long maybeApplyTranscodeLengthWorkaround(long length) {
+ // Windows truncates transferred files to the size advertised in the object property.
+ if (mStorage.isHostWindows() && isTranscodeMtpEnabled() && isFileTranscodeSupported()) {
+ // If the file supports transcoding, we double the returned size to accommodate
+ // the increase in size from transcoding to AVC. This is the same heuristic
+ // applied in the FUSE daemon (MediaProvider).
+ return length * 2;
+ }
+ return length;
+ }
+
+ private boolean isTranscodeMtpEnabled() {
+ return SystemProperties.getBoolean("sys.fuse.transcode_mtp", false);
+ }
+
+ private boolean isFileTranscodeSupported() {
+ // Check if the file supports transcoding by reading the |st_nlinks| struct stat
+ // field. This will be > 1 if the file supports transcoding. The FUSE daemon
+ // sets the field accordingly to enable the MTP stack workaround some Windows OS
+ // MTP client bug where they ignore the size returned as part of getting the MTP
+ // object, see MtpServer#doGetObject.
+ final Path path = getPath();
+ try {
+ StructStat stat = Os.stat(path.toString());
+ return stat.st_nlink > 1;
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Failed to stat path: " + getPath() + ". Ignoring transcoding.");
+ return false;
+ }
}
public Path getPath() {
@@ -420,10 +456,12 @@
* @param volume Storage to add.
* @return the associated MtpStorage
*/
- public synchronized MtpStorage addMtpStorage(StorageVolume volume) {
+ public synchronized MtpStorage addMtpStorage(StorageVolume volume,
+ Supplier<Boolean> isHostWindows) {
int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1;
- MtpStorage storage = new MtpStorage(volume, storageId);
- MtpObject root = new MtpObject(storage.getPath(), storageId, storage, null, true);
+ MtpStorage storage = new MtpStorage(volume, storageId, isHostWindows);
+ MtpObject root = new MtpObject(storage.getPath(), storageId, storage, /* parent= */ null,
+ /* isDir= */ true);
mRoots.put(storageId, root);
return storage;
}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index feae606..18b779f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -87,6 +87,7 @@
"android.hardware.drm@1.4",
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
+ "android.hardware.drm-V1-ndk",
],
header_libs: [
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/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index aa076e8..fd8a06d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -401,10 +401,14 @@
ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
const uint8_t *data, size_t numBytes, int64_t timestamp) {
- if (inputPort == nullptr || data == nullptr) {
+ if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
return AMEDIA_ERROR_INVALID_PARAMETER;
}
+ if (numBytes == 0) {
+ return 0;
+ }
+
// AMIDI_logBuffer(data, numBytes);
uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
similarity index 66%
rename from media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java
rename to media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
index fd66d3b..f23794b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.bluetooth.BluetoothProfile;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -27,22 +27,24 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
-public class BtProfileConnectionInfoTest {
+public class BluetoothProfileConnectionInfoTest {
@Test
public void testCoverageA2dp() {
final boolean supprNoisy = false;
final int volume = 42;
- final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpInfo(supprNoisy, volume);
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createA2dpInfo(supprNoisy, volume);
assertEquals(info.getProfile(), BluetoothProfile.A2DP);
- assertEquals(info.getSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
assertEquals(info.getVolume(), volume);
}
@Test
public void testCoverageA2dpSink() {
final int volume = 42;
- final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpSinkInfo(volume);
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createA2dpSinkInfo(volume);
assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK);
assertEquals(info.getVolume(), volume);
}
@@ -50,20 +52,21 @@
@Test
public void testCoveragehearingAid() {
final boolean supprNoisy = true;
- final BtProfileConnectionInfo info = BtProfileConnectionInfo.hearingAidInfo(supprNoisy);
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createHearingAidInfo(supprNoisy);
assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID);
- assertEquals(info.getSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
}
@Test
public void testCoverageLeAudio() {
final boolean supprNoisy = false;
final boolean isLeOutput = true;
- final BtProfileConnectionInfo info = BtProfileConnectionInfo.leAudio(supprNoisy,
- isLeOutput);
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createLeAudioInfo(supprNoisy, isLeOutput);
assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
- assertEquals(info.getSuppressNoisyIntent(), supprNoisy);
- assertEquals(info.getIsLeOutput(), isLeOutput);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isLeOutput(), isLeOutput);
}
}
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
index abdc7e5..a6a5568 100644
--- a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -160,10 +160,11 @@
Log.d(TAG, "sendObjectInfoChanged: " + id);
objectsInfoChanged.add(id);
}
- }, null);
+ }, /* subdirectories= */ null);
- mainMtpStorage = manager.addMtpStorage(mainStorage);
- secondaryMtpStorage = manager.addMtpStorage(secondaryStorage);
+ mainMtpStorage = manager.addMtpStorage(mainStorage, /* isHostWindows= */ () -> false);
+ secondaryMtpStorage = manager.addMtpStorage(secondaryStorage,
+ /* isHostWindows= */ () -> false);
}
@After
diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
index 414de89..0957390 100644
--- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
+++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
@@ -16,8 +16,18 @@
package android.media.audio.common;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
import android.media.AudioAttributes;
+import android.media.AudioDescriptor;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
+import android.media.AudioProfile;
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.MediaFormat;
@@ -25,11 +35,12 @@
import androidx.test.runner.AndroidJUnit4;
-import static org.junit.Assert.*;
-
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* Unit tests for AidlConversion utilities.
*
@@ -417,10 +428,102 @@
() -> AidlConversion.legacy2aidl_audio_usage_t_AudioUsage(sInvalidValue));
}
+ @Test
+ public void testAudioDescriptorConversion_Default() {
+ ExtraAudioDescriptor aidl = createDefaultDescriptor();
+ AudioDescriptor audioDescriptor =
+ AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(aidl);
+ assertEquals(AudioDescriptor.STANDARD_NONE, audioDescriptor.getStandard());
+ assertEquals(
+ AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, audioDescriptor.getEncapsulationType());
+ assertTrue(Arrays.equals(new byte[]{}, audioDescriptor.getDescriptor()));
+
+ ExtraAudioDescriptor reconstructedExtraDescriptor =
+ AidlConversion.api2aidl_AudioDescriptor_ExtraAudioDescriptor(audioDescriptor);
+ assertEquals(aidl, reconstructedExtraDescriptor);
+ }
+
+ @Test
+ public void testAudioDescriptorConversion() {
+ ExtraAudioDescriptor aidl = createEncapsulationDescriptor(new byte[]{0x05, 0x18, 0x4A});
+ AudioDescriptor audioDescriptor =
+ AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(aidl);
+ assertEquals(AudioDescriptor.STANDARD_EDID, audioDescriptor.getStandard());
+ assertEquals(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937,
+ audioDescriptor.getEncapsulationType());
+ assertTrue(Arrays.equals(new byte[]{0x05, 0x18, 0x4A}, audioDescriptor.getDescriptor()));
+
+ ExtraAudioDescriptor reconstructedExtraDescriptor =
+ AidlConversion.api2aidl_AudioDescriptor_ExtraAudioDescriptor(audioDescriptor);
+ assertEquals(aidl, reconstructedExtraDescriptor);
+ }
+
+ @Test
+ public void testAudioDeviceAttributesConversion_Default() {
+ AudioDeviceAttributes attributes =
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_DEFAULT, "myAddress");
+ AudioPort port = AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(attributes);
+ assertEquals("", port.name);
+ assertEquals(0, port.extraAudioDescriptors.length);
+ assertEquals("myAddress", port.ext.getDevice().device.address.getId());
+ assertEquals("", port.ext.getDevice().device.type.connection);
+ assertEquals(AudioDeviceType.OUT_DEFAULT, port.ext.getDevice().device.type.type);
+ }
+
+ @Test
+ public void testAudioDeviceAttributesConversion() {
+ AudioDescriptor audioDescriptor1 =
+ AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(
+ createEncapsulationDescriptor(new byte[]{0x05, 0x18, 0x4A}));
+
+ AudioDescriptor audioDescriptor2 =
+ AidlConversion.aidl2api_ExtraAudioDescriptor_AudioDescriptor(
+ createDefaultDescriptor());
+
+ AudioDeviceAttributes attributes =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HDMI_ARC, "myAddress", "myName", new ArrayList<>(),
+ new ArrayList<>(Arrays.asList(audioDescriptor1, audioDescriptor2)));
+ AudioPort port = AidlConversion.api2aidl_AudioDeviceAttributes_AudioPort(
+ attributes);
+ assertEquals("myName", port.name);
+ assertEquals(2, port.extraAudioDescriptors.length);
+ assertEquals(AudioStandard.EDID, port.extraAudioDescriptors[0].standard);
+ assertEquals(AudioEncapsulationType.IEC61937,
+ port.extraAudioDescriptors[0].encapsulationType);
+ assertTrue(Arrays.equals(new byte[]{0x05, 0x18, 0x4A},
+ port.extraAudioDescriptors[0].audioDescriptor));
+ assertEquals(AudioStandard.NONE, port.extraAudioDescriptors[1].standard);
+ assertEquals(AudioEncapsulationType.NONE,
+ port.extraAudioDescriptors[1].encapsulationType);
+ assertTrue(Arrays.equals(new byte[]{},
+ port.extraAudioDescriptors[1].audioDescriptor));
+ assertEquals("myAddress", port.ext.getDevice().device.address.getId());
+ assertEquals(AudioDeviceDescription.CONNECTION_HDMI_ARC,
+ port.ext.getDevice().device.type.connection);
+ assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type);
+ }
+
private static AudioFormatDescription createPcm16FormatAidl() {
final AudioFormatDescription aidl = new AudioFormatDescription();
aidl.type = AudioFormatType.PCM;
aidl.pcm = PcmType.INT_16_BIT;
return aidl;
}
+
+ private static ExtraAudioDescriptor createDefaultDescriptor() {
+ ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor();
+ extraDescriptor.standard = AudioStandard.NONE;
+ extraDescriptor.encapsulationType = AudioEncapsulationType.NONE;
+ extraDescriptor.audioDescriptor = new byte[]{};
+ return extraDescriptor;
+ }
+
+ private static ExtraAudioDescriptor createEncapsulationDescriptor(byte[] audioDescriptor) {
+ ExtraAudioDescriptor extraDescriptor = new ExtraAudioDescriptor();
+ extraDescriptor.standard = AudioStandard.EDID;
+ extraDescriptor.encapsulationType = AudioEncapsulationType.IEC61937;
+ extraDescriptor.audioDescriptor = audioDescriptor;
+ return extraDescriptor;
+ }
}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35c794e..b7beb6e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -309,24 +309,27 @@
android_res_nquery; # introduced=29
android_res_nresult; # introduced=29
android_res_nsend; # introduced=29
+ android_tag_socket_with_uid; # introduced=Tiramisu
+ android_tag_socket; # introduced=Tiramisu
+ android_untag_socket; # introduced=Tiramisu
AThermal_acquireManager; # introduced=30
AThermal_releaseManager; # introduced=30
AThermal_getCurrentThermalStatus; # introduced=30
AThermal_registerThermalStatusListener; # introduced=30
AThermal_unregisterThermalStatusListener; # introduced=30
AThermal_getThermalHeadroom; # introduced=31
+ APerformanceHint_getManager; # introduced=Tiramisu
+ APerformanceHint_createSession; # introduced=Tiramisu
+ APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
+ APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu
+ APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
+ APerformanceHint_closeSession; # introduced=Tiramisu
local:
*;
};
LIBANDROID_PLATFORM {
global:
- APerformanceHint_getManager;
- APerformanceHint_createSession;
- APerformanceHint_getPreferredUpdateRateNanos;
- APerformanceHint_updateTargetWorkDuration;
- APerformanceHint_reportActualWorkDuration;
- APerformanceHint_closeSession;
APerformanceHint_setIHintManagerForTesting;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 51a0c99..0c36051 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,17 +16,18 @@
#define LOG_TAG "perf_hint"
-#include <utility>
-#include <vector>
-
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <utility>
+#include <vector>
+
using namespace android;
using namespace android::os;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 284e9ee..b17850e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -18,10 +18,12 @@
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
+#include <android/performance_hint.h>
#include <binder/IBinder.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <performance_hint_private.h>
+
#include <memory>
#include <vector>
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index a0f3098..bb25274 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -198,14 +198,16 @@
return kGray_8_SkColorType;
case ANDROID_BITMAP_FORMAT_RGBA_F16:
return kRGBA_F16_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_1010102:
+ return kRGBA_1010102_SkColorType;
default:
return kUnknown_SkColorType;
}
}
int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
- if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
- || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+ if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE ||
+ format > ANDROID_BITMAP_FORMAT_RGBA_1010102) {
return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
}
@@ -290,6 +292,8 @@
return ANDROID_BITMAP_FORMAT_A_8;
case kRGBA_F16_SkColorType:
return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ case kRGBA_1010102_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_1010102;
default:
return ANDROID_BITMAP_FORMAT_NONE;
}
diff --git a/omapi/OWNERS b/omapi/OWNERS
new file mode 100644
index 0000000..5682fd3
--- /dev/null
+++ b/omapi/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 456592
+
+zachoverflow@google.com
+alisher@google.com
+jackcwyu@google.com
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 5944dba..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..926f715 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 b58ebd3..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/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 223bdcdd..6329565 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -39,7 +39,6 @@
"src/android/net/TrafficStats.java",
"src/android/net/UnderlyingNetworkInfo.*",
"src/android/net/netstats/**/*.*",
- "src/com/android/server/NetworkManagementSocketTagger.java",
],
path: "src",
visibility: [
@@ -126,14 +125,14 @@
name: "framework-connectivity-ethernet-sources",
srcs: [
"src/android/net/EthernetManager.java",
+ "src/android/net/EthernetNetworkManagementException.java",
+ "src/android/net/EthernetNetworkManagementException.aidl",
"src/android/net/EthernetNetworkSpecifier.java",
+ "src/android/net/EthernetNetworkUpdateRequest.java",
+ "src/android/net/EthernetNetworkUpdateRequest.aidl",
"src/android/net/IEthernetManager.aidl",
+ "src/android/net/IEthernetNetworkManagementListener.aidl",
"src/android/net/IEthernetServiceListener.aidl",
- "src/android/net/IInternalNetworkManagementListener.aidl",
- "src/android/net/InternalNetworkUpdateRequest.java",
- "src/android/net/InternalNetworkUpdateRequest.aidl",
- "src/android/net/InternalNetworkManagementException.java",
- "src/android/net/InternalNetworkManagementException.aidl",
"src/android/net/ITetheredInterfaceCallback.aidl",
],
path: "src",
@@ -159,7 +158,6 @@
name: "framework-connectivity-tiramisu-sources",
srcs: [
":framework-connectivity-ethernet-sources",
- ":framework-connectivity-ipsec-sources",
":framework-connectivity-netstats-sources",
],
visibility: ["//frameworks/base"],
@@ -168,6 +166,7 @@
filegroup {
name: "framework-connectivity-tiramisu-updatable-sources",
srcs: [
+ ":framework-connectivity-ipsec-sources",
":framework-connectivity-nsd-sources",
":framework-connectivity-tiramisu-internal-sources",
],
@@ -176,3 +175,34 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+cc_library_shared {
+ name: "libframework-connectivity-tiramisu-jni",
+ min_sdk_version: "30",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ // Don't warn about S API usage even with
+ // min_sdk 30: the library is only loaded
+ // on S+ devices
+ "-Wno-unguarded-availability",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "jni/android_net_TrafficStats.cpp",
+ "jni/onload.cpp",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "libnativehelper_compat_libc++",
+ ],
+ stl: "none",
+ apex_available: [
+ "com.android.tethering",
+ // TODO: remove when ConnectivityT moves to APEX.
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..f3c58b1
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_tag_socket_with_uid(fd, tag, uid);
+}
+
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
+ int fd = AFileDescriptor_getFd(env, fileDescriptor);
+ if (fd == -1) return -EBADF;
+ return android_untag_socket(fd);
+}
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
+ { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/packages/ConnectivityT/framework-t/jni/onload.cpp b/packages/ConnectivityT/framework-t/jni/onload.cpp
new file mode 100644
index 0000000..1fb42c6
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/jni/onload.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FrameworkConnectivityJNI"
+
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+int register_android_net_TrafficStats(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
+
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..5ce7e59 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -41,6 +41,7 @@
import android.net.NetworkTemplate;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
import android.os.Build;
@@ -126,17 +127,12 @@
private final INetworkStatsService mService;
/**
- * Type constants for reading different types of Data Usage.
+ * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT}
+ * instead.
* @hide
*/
- // @SystemApi(client = MODULE_LIBRARIES)
+ @Deprecated
public static final String PREFIX_DEV = "dev";
- /** @hide */
- public static final String PREFIX_XT = "xt";
- /** @hide */
- public static final String PREFIX_UID = "uid";
- /** @hide */
- public static final String PREFIX_UID_TAG = "uid_tag";
/** @hide */
public static final int FLAG_POLL_ON_OPEN = 1 << 0;
@@ -145,6 +141,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 +1119,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/ConnectivityFrameworkInitializerTiramisu.java b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
index 630f902e..577ac54 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -48,5 +48,14 @@
return new NsdManager(context, service);
}
);
+
+ SystemServiceRegistry.registerContextAwareService(
+ Context.IPSEC_SERVICE,
+ IpSecManager.class,
+ (context, serviceBinder) -> {
+ IIpSecService service = IIpSecService.Stub.asInterface(serviceBinder);
+ return new IpSecManager(context, service);
+ }
+ );
}
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index ece54df..e0ce081 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -320,15 +320,15 @@
}
private static final class InternalNetworkManagementListener
- extends IInternalNetworkManagementListener.Stub {
+ extends IEthernetNetworkManagementListener.Stub {
@NonNull
private final Executor mExecutor;
@NonNull
- private final BiConsumer<Network, InternalNetworkManagementException> mListener;
+ private final BiConsumer<Network, EthernetNetworkManagementException> mListener;
InternalNetworkManagementListener(
@NonNull final Executor executor,
- @NonNull final BiConsumer<Network, InternalNetworkManagementException> listener) {
+ @NonNull final BiConsumer<Network, EthernetNetworkManagementException> listener) {
Objects.requireNonNull(executor, "Pass a non-null executor");
Objects.requireNonNull(listener, "Pass a non-null listener");
mExecutor = executor;
@@ -338,14 +338,14 @@
@Override
public void onComplete(
@Nullable final Network network,
- @Nullable final InternalNetworkManagementException e) {
+ @Nullable final EthernetNetworkManagementException e) {
mExecutor.execute(() -> mListener.accept(network, e));
}
}
private InternalNetworkManagementListener getInternalNetworkManagementListener(
@Nullable final Executor executor,
- @Nullable final BiConsumer<Network, InternalNetworkManagementException> listener) {
+ @Nullable final BiConsumer<Network, EthernetNetworkManagementException> listener) {
if (null != listener) {
Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
}
@@ -358,11 +358,12 @@
return proxy;
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS)
private void updateConfiguration(
@NonNull String iface,
- @NonNull InternalNetworkUpdateRequest request,
+ @NonNull EthernetNetworkUpdateRequest request,
@Nullable @CallbackExecutor Executor executor,
- @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
executor, listener);
try {
@@ -372,10 +373,11 @@
}
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS)
private void connectNetwork(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
- @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
executor, listener);
try {
@@ -385,10 +387,11 @@
}
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_ETHERNET_NETWORKS)
private void disconnectNetwork(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
- @Nullable BiConsumer<Network, InternalNetworkManagementException> listener) {
+ @Nullable BiConsumer<Network, EthernetNetworkManagementException> listener) {
final InternalNetworkManagementListener proxy = getInternalNetworkManagementListener(
executor, listener);
try {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
index dcce706..adf9e5a 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.aidl
@@ -16,4 +16,4 @@
package android.net;
- parcelable InternalNetworkManagementException;
\ No newline at end of file
+ parcelable EthernetNetworkManagementException;
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
new file mode 100644
index 0000000..a35f28e
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkManagementException.java
@@ -0,0 +1,71 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/** @hide */
+public final class EthernetNetworkManagementException
+ extends RuntimeException implements Parcelable {
+
+ /* @hide */
+ public EthernetNetworkManagementException(@NonNull final String errorMessage) {
+ super(errorMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getMessage());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj;
+
+ return Objects.equals(getMessage(), that.getMessage());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(getMessage());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR =
+ new Parcelable.Creator<EthernetNetworkManagementException>() {
+ @Override
+ public EthernetNetworkManagementException[] newArray(int size) {
+ return new EthernetNetworkManagementException[size];
+ }
+
+ @Override
+ public EthernetNetworkManagementException createFromParcel(@NonNull Parcel source) {
+ return new EthernetNetworkManagementException(source.readString());
+ }
+ };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
similarity index 93%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
index da00cb9..debc348 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.aidl
@@ -16,4 +16,4 @@
package android.net;
- parcelable InternalNetworkUpdateRequest;
\ No newline at end of file
+ parcelable EthernetNetworkUpdateRequest;
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
index f42c4b7..4d229d2 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java
@@ -23,7 +23,7 @@
import java.util.Objects;
/** @hide */
-public final class InternalNetworkUpdateRequest implements Parcelable {
+public final class EthernetNetworkUpdateRequest implements Parcelable {
@NonNull
private final StaticIpConfiguration mIpConfig;
@NonNull
@@ -40,7 +40,7 @@
}
/** @hide */
- public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
+ public EthernetNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
@NonNull final NetworkCapabilities networkCapabilities) {
Objects.requireNonNull(ipConfig);
Objects.requireNonNull(networkCapabilities);
@@ -48,7 +48,7 @@
mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
}
- private InternalNetworkUpdateRequest(@NonNull final Parcel source) {
+ private EthernetNetworkUpdateRequest(@NonNull final Parcel source) {
Objects.requireNonNull(source);
mIpConfig = StaticIpConfiguration.CREATOR.createFromParcel(source);
mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source);
@@ -56,7 +56,7 @@
@Override
public String toString() {
- return "InternalNetworkUpdateRequest{"
+ return "EthernetNetworkUpdateRequest{"
+ "mIpConfig=" + mIpConfig
+ ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
}
@@ -65,7 +65,7 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- InternalNetworkUpdateRequest that = (InternalNetworkUpdateRequest) o;
+ EthernetNetworkUpdateRequest that = (EthernetNetworkUpdateRequest) o;
return Objects.equals(that.getIpConfig(), mIpConfig)
&& Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities);
@@ -88,16 +88,16 @@
}
@NonNull
- public static final Parcelable.Creator<InternalNetworkUpdateRequest> CREATOR =
- new Parcelable.Creator<InternalNetworkUpdateRequest>() {
+ public static final Parcelable.Creator<EthernetNetworkUpdateRequest> CREATOR =
+ new Parcelable.Creator<EthernetNetworkUpdateRequest>() {
@Override
- public InternalNetworkUpdateRequest[] newArray(int size) {
- return new InternalNetworkUpdateRequest[size];
+ public EthernetNetworkUpdateRequest[] newArray(int size) {
+ return new EthernetNetworkUpdateRequest[size];
}
@Override
- public InternalNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
- return new InternalNetworkUpdateRequest(source);
+ public EthernetNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
+ return new EthernetNetworkUpdateRequest(source);
}
};
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
index e688bea..544d02b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
@@ -18,8 +18,8 @@
import android.net.IpConfiguration;
import android.net.IEthernetServiceListener;
-import android.net.IInternalNetworkManagementListener;
-import android.net.InternalNetworkUpdateRequest;
+import android.net.IEthernetNetworkManagementListener;
+import android.net.EthernetNetworkUpdateRequest;
import android.net.ITetheredInterfaceCallback;
/**
@@ -38,8 +38,8 @@
void setIncludeTestInterfaces(boolean include);
void requestTetheredInterface(in ITetheredInterfaceCallback callback);
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
- void updateConfiguration(String iface, in InternalNetworkUpdateRequest request,
- in IInternalNetworkManagementListener listener);
- void connectNetwork(String iface, in IInternalNetworkManagementListener listener);
- void disconnectNetwork(String iface, in IInternalNetworkManagementListener listener);
+ void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
+ in IEthernetNetworkManagementListener listener);
+ void connectNetwork(String iface, in IEthernetNetworkManagementListener listener);
+ void disconnectNetwork(String iface, in IEthernetNetworkManagementListener listener);
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
similarity index 80%
rename from packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
index 69cde3b..93edccf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetNetworkManagementListener.aidl
@@ -16,10 +16,10 @@
package android.net;
-import android.net.InternalNetworkManagementException;
+import android.net.EthernetNetworkManagementException;
import android.net.Network;
/** @hide */
-oneway interface IInternalNetworkManagementListener {
- void onComplete(in Network network, in InternalNetworkManagementException exception);
+oneway interface IEthernetNetworkManagementListener {
+ void onComplete(in Network network, in EthernetNetworkManagementException exception);
}
\ No newline at end of file
diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
deleted file mode 100644
index 7f4e403..0000000
--- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/** @hide */
-public final class InternalNetworkManagementException
- extends RuntimeException implements Parcelable {
-
- /* @hide */
- public InternalNetworkManagementException(@NonNull final Throwable t) {
- super(t);
- }
-
- private InternalNetworkManagementException(@NonNull final Parcel source) {
- super(source.readString());
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(getCause().getMessage());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @NonNull
- public static final Parcelable.Creator<InternalNetworkManagementException> CREATOR =
- new Parcelable.Creator<InternalNetworkManagementException>() {
- @Override
- public InternalNetworkManagementException[] newArray(int size) {
- return new InternalNetworkManagementException[size];
- }
-
- @Override
- public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) {
- return new InternalNetworkManagementException(source);
- }
- };
-}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 77fc171..56faa52 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -26,10 +26,10 @@
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;
-import android.telephony.Annotation;
import android.telephony.TelephonyManager;
import android.util.proto.ProtoOutputStream;
@@ -274,8 +274,7 @@
@Deprecated
@NonNull
public static NetworkIdentity buildNetworkIdentity(Context context,
- @NonNull NetworkStateSnapshot snapshot,
- boolean defaultNetwork, @Annotation.NetworkType int ratType) {
+ @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
.setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
@@ -432,10 +431,10 @@
* @return this builder.
*/
@NonNull
- public Builder setRatType(@Annotation.NetworkType int ratType) {
+ public Builder setRatType(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..9b58b01 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -41,13 +41,12 @@
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 +57,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 +133,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)
@@ -214,7 +202,7 @@
* @hide
*/
public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
- @NetworkType int ratType, int metered) {
+ int ratType, int metered) {
if (TextUtils.isEmpty(subscriberId)) {
return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */,
null /* matchSubscriberIds */,
@@ -711,7 +699,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 +744,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) {
@@ -1127,7 +1038,7 @@
* @return this builder.
*/
@NonNull
- public Builder setRatType(@NetworkType int ratType) {
+ public Builder setRatType(int ratType) {
// Input will be validated with the match rule when building the template.
mRatType = ratType;
return this;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c2f0cdf..bc836d8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -31,12 +31,9 @@
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.util.Log;
-import com.android.server.NetworkManagementSocketTagger;
-
-import dalvik.system.SocketTagger;
-
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
@@ -56,6 +53,10 @@
* use {@link NetworkStatsManager} instead.
*/
public class TrafficStats {
+ static {
+ System.loadLibrary("framework-connectivity-tiramisu-jni");
+ }
+
private static final String TAG = TrafficStats.class.getSimpleName();
/**
* The return value to indicate that the device does not support the statistic.
@@ -232,9 +233,68 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
public static void attachSocketTagger() {
- NetworkManagementSocketTagger.install();
+ dalvik.system.SocketTagger.set(new SocketTagger());
}
+ private static class SocketTagger extends dalvik.system.SocketTagger {
+
+ // TODO: set to false
+ private static final boolean LOGD = true;
+
+ SocketTagger() {
+ }
+
+ @Override
+ public void tag(FileDescriptor fd) throws SocketException {
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (LOGD) {
+ Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
+ + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid);
+ }
+ if (tagInfo.tag == -1) {
+ StrictMode.noteUntaggedSocket();
+ }
+
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+ final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid);
+ if (errno < 0) {
+ Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
+ + tagInfo.tag + ", "
+ + tagInfo.uid + ") failed with errno" + errno);
+ }
+ }
+
+ @Override
+ public void untag(FileDescriptor fd) throws SocketException {
+ if (LOGD) {
+ Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
+ }
+
+ final UidTag tagInfo = sThreadUidTag.get();
+ if (tagInfo.tag == -1 && tagInfo.uid == -1) return;
+
+ final int errno = native_untagSocketFd(fd);
+ if (errno < 0) {
+ Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
+ }
+ }
+ }
+
+ private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
+ private static native int native_untagSocketFd(FileDescriptor fd);
+
+ private static class UidTag {
+ public int tag = -1;
+ public int uid = -1;
+ }
+
+ private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() {
+ @Override
+ protected UidTag initialValue() {
+ return new UidTag();
+ }
+ };
+
/**
* Set active tag to use when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
@@ -249,7 +309,7 @@
* @see #clearThreadStatsTag()
*/
public static void setThreadStatsTag(int tag) {
- NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ getAndSetThreadStatsTag(tag);
}
/**
@@ -267,7 +327,9 @@
* restore any existing values after a nested operation is finished
*/
public static int getAndSetThreadStatsTag(int tag) {
- return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ final int old = sThreadUidTag.get().tag;
+ sThreadUidTag.get().tag = tag;
+ return old;
}
/**
@@ -327,7 +389,7 @@
* @see #setThreadStatsTag(int)
*/
public static int getThreadStatsTag() {
- return NetworkManagementSocketTagger.getThreadSocketStatsTag();
+ return sThreadUidTag.get().tag;
}
/**
@@ -337,7 +399,7 @@
* @see #setThreadStatsTag(int)
*/
public static void clearThreadStatsTag() {
- NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
+ sThreadUidTag.get().tag = -1;
}
/**
@@ -357,7 +419,7 @@
*/
@SuppressLint("RequiresPermission")
public static void setThreadStatsUid(int uid) {
- NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
+ sThreadUidTag.get().uid = uid;
}
/**
@@ -368,7 +430,7 @@
* @see #setThreadStatsUid(int)
*/
public static int getThreadStatsUid() {
- return NetworkManagementSocketTagger.getThreadSocketStatsUid();
+ return sThreadUidTag.get().uid;
}
/**
@@ -395,7 +457,7 @@
*/
@SuppressLint("RequiresPermission")
public static void clearThreadStatsUid() {
- NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
+ setThreadStatsUid(-1);
}
/**
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 0f21e55..512fbce 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,9 @@
package android.net.nsd;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
@@ -23,15 +26,22 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
@@ -278,9 +288,180 @@
private final SparseArray mListenerMap = new SparseArray();
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
private final Object mMapLock = new Object();
+ // Map of listener key sent by client -> per-network discovery tracker
+ @GuardedBy("mPerNetworkDiscoveryMap")
+ private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
+ mPerNetworkDiscoveryMap = new ArrayMap<>();
private final ServiceHandler mHandler;
+ private class PerNetworkDiscoveryTracker {
+ final String mServiceType;
+ final int mProtocolType;
+ final DiscoveryListener mBaseListener;
+ final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
+ new ArrayMap<>();
+
+ final NetworkCallback mNetworkCb = new NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
+ network, mBaseListener);
+ mPerNetworkListeners.put(network, wrappedListener);
+ discoverServices(mServiceType, mProtocolType, network, wrappedListener);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ final DelegatingDiscoveryListener listener = mPerNetworkListeners.get(network);
+ if (listener == null) return;
+ listener.notifyAllServicesLost();
+ // Listener will be removed from map in discovery stopped callback
+ stopServiceDiscovery(listener);
+ }
+ };
+
+ // Accessed from mHandler
+ private boolean mStopRequested;
+
+ public void start(@NonNull NetworkRequest request) {
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ cm.registerNetworkCallback(request, mNetworkCb, mHandler);
+ mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType));
+ }
+
+ /**
+ * Stop discovery on all networks tracked by this class.
+ *
+ * This will request all underlying listeners to stop, and the last one to stop will call
+ * onDiscoveryStopped or onStopDiscoveryFailed.
+ *
+ * Must be called on the handler thread.
+ */
+ public void requestStop() {
+ mHandler.post(() -> {
+ mStopRequested = true;
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ cm.unregisterNetworkCallback(mNetworkCb);
+ if (mPerNetworkListeners.size() == 0) {
+ mBaseListener.onDiscoveryStopped(mServiceType);
+ return;
+ }
+ for (int i = 0; i < mPerNetworkListeners.size(); i++) {
+ final DelegatingDiscoveryListener listener = mPerNetworkListeners.valueAt(i);
+ stopServiceDiscovery(listener);
+ }
+ });
+ }
+
+ private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
+ DiscoveryListener baseListener) {
+ mServiceType = serviceType;
+ mProtocolType = protocolType;
+ mBaseListener = baseListener;
+ }
+
+ /**
+ * Subset of NsdServiceInfo that is tracked to generate service lost notifications when a
+ * network is lost.
+ *
+ * Service lost notifications only contain service name, type and network, so only track
+ * that information (Network is known from the listener). This also implements
+ * equals/hashCode for usage in maps.
+ */
+ private class TrackedNsdInfo {
+ private final String mServiceName;
+ private final String mServiceType;
+ TrackedNsdInfo(NsdServiceInfo info) {
+ mServiceName = info.getServiceName();
+ mServiceType = info.getServiceType();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mServiceName, mServiceType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TrackedNsdInfo)) return false;
+ final TrackedNsdInfo other = (TrackedNsdInfo) obj;
+ return Objects.equals(mServiceName, other.mServiceName)
+ && Objects.equals(mServiceType, other.mServiceType);
+ }
+ }
+
+ private class DelegatingDiscoveryListener implements DiscoveryListener {
+ private final Network mNetwork;
+ private final DiscoveryListener mWrapped;
+ private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
+
+ private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) {
+ mNetwork = network;
+ mWrapped = listener;
+ }
+
+ void notifyAllServicesLost() {
+ for (int i = 0; i < mFoundInfo.size(); i++) {
+ final TrackedNsdInfo trackedInfo = mFoundInfo.valueAt(i);
+ final NsdServiceInfo serviceInfo = new NsdServiceInfo(
+ trackedInfo.mServiceName, trackedInfo.mServiceType);
+ serviceInfo.setNetwork(mNetwork);
+ mWrapped.onServiceLost(serviceInfo);
+ }
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ // The delegated listener is used when NsdManager takes care of starting/stopping
+ // discovery on multiple networks. Failure to start on one network is not a global
+ // failure to be reported up, as other networks may succeed: just log.
+ Log.e(TAG, "Failed to start discovery for " + serviceType + " on " + mNetwork
+ + " with code " + errorCode);
+ mPerNetworkListeners.remove(mNetwork);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ // Wrapped listener was called upon registration, it is not called for discovery
+ // on each network
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(TAG, "Failed to stop discovery for " + serviceType + " on " + mNetwork
+ + " with code " + errorCode);
+ mPerNetworkListeners.remove(mNetwork);
+ if (mStopRequested && mPerNetworkListeners.size() == 0) {
+ // Do not report onStopDiscoveryFailed when some underlying listeners failed:
+ // this does not mean that all listeners did, and onStopDiscoveryFailed is not
+ // actionable anyway. Just report that discovery stopped.
+ mWrapped.onDiscoveryStopped(serviceType);
+ }
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ mPerNetworkListeners.remove(mNetwork);
+ if (mStopRequested && mPerNetworkListeners.size() == 0) {
+ mWrapped.onDiscoveryStopped(serviceType);
+ }
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
+ mWrapped.onServiceFound(serviceInfo);
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
+ mWrapped.onServiceLost(serviceInfo);
+ }
+ }
+ }
+
/**
* Create a new Nsd instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -634,6 +815,14 @@
}
/**
+ * Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null
+ * {@link Network}.
+ */
+ public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+ discoverServices(serviceType, protocolType, (Network) null, listener);
+ }
+
+ /**
* Initiate service discovery to browse for instances of a service type. Service discovery
* consumes network bandwidth and will continue until the application calls
* {@link #stopServiceDiscovery}.
@@ -657,11 +846,13 @@
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
* http services or "_ipp._tcp" for printers
* @param protocolType The service discovery protocol
+ * @param network Network to discover services on, or null to discover on all available networks
* @param listener The listener notifies of a successful discovery and is used
* to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
* Cannot be null. Cannot be in use for an active service discovery.
*/
- public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+ public void discoverServices(@NonNull String serviceType, int protocolType,
+ @Nullable Network network, @NonNull DiscoveryListener listener) {
if (TextUtils.isEmpty(serviceType)) {
throw new IllegalArgumentException("Service type cannot be empty");
}
@@ -669,6 +860,7 @@
NsdServiceInfo s = new NsdServiceInfo();
s.setServiceType(serviceType);
+ s.setNetwork(network);
int key = putListener(listener, s);
try {
@@ -679,6 +871,67 @@
}
/**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+ * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ *
+ * <p> During discovery, new networks may connect or existing networks may disconnect - for
+ * example if wifi is reconnected. When a service was found on a network that disconnects,
+ * {@link DiscoveryListener#onServiceLost} will be called. If a new network connects that
+ * matches the {@link NetworkRequest}, {@link DiscoveryListener#onServiceFound} will be called
+ * for services found on that network. Applications that do not want to track networks
+ * themselves are encouraged to use this method instead of other overloads of
+ * {@code discoverServices}, as they will receive proper notifications when a service becomes
+ * available or unavailable due to network changes.
+ *
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param protocolType The service discovery protocol
+ * @param networkRequest Request specifying networks that should be considered when discovering
+ * @param listener The listener notifies of a successful discovery and is used
+ * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+ * Cannot be null. Cannot be in use for an active service discovery.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void discoverServices(@NonNull String serviceType, int protocolType,
+ @NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) {
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service type cannot be empty");
+ }
+ Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null");
+ checkProtocol(protocolType);
+
+ NsdServiceInfo s = new NsdServiceInfo();
+ s.setServiceType(serviceType);
+
+ final int baseListenerKey = putListener(listener, s);
+
+ final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
+ serviceType, protocolType, listener);
+
+ synchronized (mPerNetworkDiscoveryMap) {
+ mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
+ discoveryInfo.start(networkRequest);
+ }
+ }
+
+ /**
* Stop service discovery initiated with {@link #discoverServices}. An active service
* discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
* and it stays active until the application invokes a stop service discovery. A successful
@@ -696,6 +949,14 @@
*/
public void stopServiceDiscovery(DiscoveryListener listener) {
int id = getListenerKey(listener);
+ // If this is a PerNetworkDiscovery request, handle it as such
+ synchronized (mPerNetworkDiscoveryMap) {
+ final PerNetworkDiscoveryTracker info = mPerNetworkDiscoveryMap.get(id);
+ if (info != null) {
+ info.requestStop();
+ return;
+ }
+ }
try {
mService.stopDiscovery(id);
} catch (RemoteException e) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 0946499..8506db1 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -17,7 +17,9 @@
package android.net.nsd;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Network;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -49,6 +51,9 @@
private int mPort;
+ @Nullable
+ private Network mNetwork;
+
public NsdServiceInfo() {
}
@@ -307,18 +312,37 @@
return txtRecord;
}
- public String toString() {
- StringBuffer sb = new StringBuffer();
+ /**
+ * Get the network where the service can be found.
+ *
+ * This is never null if this {@link NsdServiceInfo} was obtained from
+ * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return mNetwork;
+ }
+ /**
+ * Set the network where the service can be found.
+ * @param network The network, or null to search for, or to announce, the service on all
+ * connected networks.
+ */
+ public void setNetwork(@Nullable Network network) {
+ mNetwork = network;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
sb.append("name: ").append(mServiceName)
.append(", type: ").append(mServiceType)
.append(", host: ").append(mHost)
- .append(", port: ").append(mPort);
+ .append(", port: ").append(mPort)
+ .append(", network: ").append(mNetwork);
byte[] txtRecord = getTxtRecord();
- if (txtRecord != null) {
- sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
- }
+ sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
return sb.toString();
}
@@ -352,6 +376,8 @@
}
dest.writeString(key);
}
+
+ dest.writeParcelable(mNetwork, 0);
}
/** Implement the Parcelable interface */
@@ -381,6 +407,7 @@
}
info.mTxtRecord.put(in.readString(), valueArray);
}
+ info.mNetwork = in.readParcelable(null, Network.class);
return info;
}
diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
deleted file mode 100644
index 1eb52fb..0000000
--- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.os.StrictMode;
-import android.util.Log;
-
-import dalvik.system.SocketTagger;
-
-import java.io.FileDescriptor;
-import java.net.SocketException;
-
-/**
- * Assigns tags to sockets for traffic stats.
- * @hide
- */
-public final class NetworkManagementSocketTagger extends SocketTagger {
- private static final String TAG = "NetworkManagementSocketTagger";
- private static final boolean LOGD = false;
-
- private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
- @Override
- protected SocketTags initialValue() {
- return new SocketTags();
- }
- };
-
- public static void install() {
- SocketTagger.set(new NetworkManagementSocketTagger());
- }
-
- public static int setThreadSocketStatsTag(int tag) {
- final int old = threadSocketTags.get().statsTag;
- threadSocketTags.get().statsTag = tag;
- return old;
- }
-
- public static int getThreadSocketStatsTag() {
- return threadSocketTags.get().statsTag;
- }
-
- public static int setThreadSocketStatsUid(int uid) {
- final int old = threadSocketTags.get().statsUid;
- threadSocketTags.get().statsUid = uid;
- return old;
- }
-
- public static int getThreadSocketStatsUid() {
- return threadSocketTags.get().statsUid;
- }
-
- @Override
- public void tag(FileDescriptor fd) throws SocketException {
- final SocketTags options = threadSocketTags.get();
- if (LOGD) {
- Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x"
- + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid);
- }
- if (options.statsTag == -1) {
- StrictMode.noteUntaggedSocket();
- }
- // TODO: skip tagging when options would be no-op
- tagSocketFd(fd, options.statsTag, options.statsUid);
- }
-
- private void tagSocketFd(FileDescriptor fd, int tag, int uid) {
- if (tag == -1 && uid == -1) return;
-
- final int errno = native_tagSocketFd(fd, tag, uid);
- if (errno < 0) {
- Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", "
- + tag + ", "
- + uid + ") failed with errno" + errno);
- }
- }
-
- @Override
- public void untag(FileDescriptor fd) throws SocketException {
- if (LOGD) {
- Log.i(TAG, "untagSocket(" + fd.getInt$() + ")");
- }
- unTagSocketFd(fd);
- }
-
- private void unTagSocketFd(FileDescriptor fd) {
- final SocketTags options = threadSocketTags.get();
- if (options.statsTag == -1 && options.statsUid == -1) return;
-
- final int errno = native_untagSocketFd(fd);
- if (errno < 0) {
- Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno);
- }
- }
-
- public static class SocketTags {
- public int statsTag = -1;
- public int statsUid = -1;
- }
-
- public static void setKernelCounterSet(int uid, int counterSet) {
- final int errno = native_setCounterSet(counterSet, uid);
- if (errno < 0) {
- Log.w(TAG, "setKernelCountSet(" + uid + ", " + counterSet + ") failed with errno "
- + errno);
- }
- }
-
- public static void resetKernelUidStats(int uid) {
- int errno = native_deleteTagData(0, uid);
- if (errno < 0) {
- Log.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno);
- }
- }
-
- /**
- * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
- * format like {@code 0x7fffffff00000000}.
- */
- public static int kernelToTag(String string) {
- int length = string.length();
- if (length > 10) {
- return Long.decode(string.substring(0, length - 8)).intValue();
- } else {
- return 0;
- }
- }
-
- private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid);
- private static native int native_untagSocketFd(FileDescriptor fd);
- private static native int native_setCounterSet(int uid, int counterSetNum);
- private static native int native_deleteTagData(int tag, int uid);
-}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 24bc91d..d2d6ef0 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -83,7 +83,6 @@
name: "services.connectivity-tiramisu-sources",
srcs: [
":services.connectivity-ethernet-sources",
- ":services.connectivity-ipsec-sources",
":services.connectivity-netstats-sources",
],
path: "src",
@@ -93,6 +92,7 @@
filegroup {
name: "services.connectivity-tiramisu-updatable-sources",
srcs: [
+ ":services.connectivity-ipsec-sources",
":services.connectivity-nsd-sources",
],
path: "src",
diff --git a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
index 179d945..4bc40ea 100644
--- a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
@@ -1008,16 +1008,10 @@
*
* @param context Binder context for this service
*/
- private IpSecService(Context context) {
+ public IpSecService(Context context) {
this(context, new Dependencies());
}
- static IpSecService create(Context context)
- throws InterruptedException {
- final IpSecService service = new IpSecService(context);
- return service;
- }
-
@NonNull
private AppOpsManager getAppOpsManager() {
AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1054,26 +1048,6 @@
}
}
- /** Called by system server when system is ready. */
- public void systemReady() {
- if (isNetdAlive()) {
- Log.d(TAG, "IpSecService is ready");
- } else {
- Log.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
- }
- }
-
- synchronized boolean isNetdAlive() {
- try {
- if (mNetd == null) {
- return false;
- }
- return mNetd.isAlive();
- } catch (RemoteException re) {
- return false;
- }
- }
-
/**
* Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
* a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
@@ -1896,7 +1870,6 @@
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
pw.println("IpSecService dump:");
- pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
pw.println();
pw.println("mUserResourceTracker:");
diff --git a/packages/ConnectivityT/service/src/com/android/server/NsdService.java b/packages/ConnectivityT/service/src/com/android/server/NsdService.java
index 497107d..ddf6d2c 100644
--- a/packages/ConnectivityT/service/src/com/android/server/NsdService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/NsdService.java
@@ -19,6 +19,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
@@ -44,6 +47,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -60,6 +66,7 @@
private static final boolean DBG = true;
private static final long CLEANUP_DELAY_MS = 10000;
+ private static final int IFACE_IDX_ANY = 0;
private final Context mContext;
private final NsdStateMachine mNsdStateMachine;
@@ -297,7 +304,7 @@
maybeStartDaemon();
id = getUniqueId();
- if (discoverServices(id, args.serviceInfo.getServiceType())) {
+ if (discoverServices(id, args.serviceInfo)) {
if (DBG) {
Log.d(TAG, "Discover " + msg.arg2 + " " + id
+ args.serviceInfo.getServiceType());
@@ -430,13 +437,38 @@
}
switch (code) {
case NativeResponseCode.SERVICE_FOUND:
- /* NNN uniqueId serviceName regType domain */
+ /* NNN uniqueId serviceName regType domain interfaceIdx netId */
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
+ final int foundNetId;
+ try {
+ foundNetId = Integer.parseInt(cooked[6]);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
+ break;
+ }
+ if (foundNetId == 0L) {
+ // Ignore services that do not have a Network: they are not usable
+ // by apps, as they would need privileged permissions to use
+ // interfaces that do not have an associated Network.
+ break;
+ }
+ servInfo.setNetwork(new Network(foundNetId));
clientInfo.onServiceFound(clientId, servInfo);
break;
case NativeResponseCode.SERVICE_LOST:
- /* NNN uniqueId serviceName regType domain */
+ /* NNN uniqueId serviceName regType domain interfaceIdx netId */
+ final int lostNetId;
+ try {
+ lostNetId = Integer.parseInt(cooked[6]);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
+ break;
+ }
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
+ // The network could be null if it was torn down when the service is lost
+ // TODO: avoid returning null in that case, possibly by remembering found
+ // services on the same interface index and their network at the time
+ servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId));
clientInfo.onServiceLost(clientId, servInfo);
break;
case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
@@ -461,7 +493,7 @@
/* NNN regId errorCode */
break;
case NativeResponseCode.SERVICE_RESOLVED:
- /* NNN resolveId fullName hostName port txtlen txtdata */
+ /* NNN resolveId fullName hostName port txtlen txtdata interfaceIdx */
int index = 0;
while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
if (cooked[2].charAt(index) == '\\') {
@@ -473,6 +505,7 @@
Log.e(TAG, "Invalid service found " + raw);
break;
}
+
String name = cooked[2].substring(0, index);
String rest = cooked[2].substring(index);
String type = rest.replace(".local.", "");
@@ -483,12 +516,13 @@
clientInfo.mResolvedService.setServiceType(type);
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
clientInfo.mResolvedService.setTxtRecords(cooked[6]);
+ // Network will be added after SERVICE_GET_ADDR_SUCCESS
stopResolveService(id);
removeRequestMap(clientId, id, clientInfo);
int id2 = getUniqueId();
- if (getAddrInfo(id2, cooked[3])) {
+ if (getAddrInfo(id2, cooked[3], cooked[7] /* interfaceIdx */)) {
storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
} else {
clientInfo.onResolveServiceFailed(
@@ -513,12 +547,31 @@
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
- /* NNN resolveId hostname ttl addr */
+ /* NNN resolveId hostname ttl addr interfaceIdx netId */
+ Network network = null;
try {
- clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
+ final int netId = Integer.parseInt(cooked[6]);
+ network = netId == 0L ? null : new Network(netId);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Invalid network in GET_ADDR_SUCCESS: " + cooked[6], e);
+ }
+
+ InetAddress serviceHost = null;
+ try {
+ serviceHost = InetAddress.getByName(cooked[4]);
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e);
+ }
+
+ // If the resolved service is on an interface without a network, consider it
+ // as a failure: it would not be usable by apps as they would need
+ // privileged permissions.
+ if (network != null && serviceHost != null) {
+ clientInfo.mResolvedService.setHost(serviceHost);
+ clientInfo.mResolvedService.setNetwork(network);
clientInfo.onResolveServiceSucceeded(
clientId, clientInfo.mResolvedService);
- } catch (java.net.UnknownHostException e) {
+ } else {
clientInfo.onResolveServiceFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
}
@@ -815,8 +868,15 @@
return mDaemon.execute("update", regId, t.size(), t.getRawData());
}
- private boolean discoverServices(int discoveryId, String serviceType) {
- return mDaemon.execute("discover", discoveryId, serviceType);
+ private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
+ final Network network = serviceInfo.getNetwork();
+ final int discoverInterface = getNetworkInterfaceIndex(network);
+ if (network != null && discoverInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to discover service on not found");
+ return false;
+ }
+ return mDaemon.execute("discover", discoveryId, serviceInfo.getServiceType(),
+ discoverInterface);
}
private boolean stopServiceDiscovery(int discoveryId) {
@@ -824,17 +884,61 @@
}
private boolean resolveService(int resolveId, NsdServiceInfo service) {
- String name = service.getServiceName();
- String type = service.getServiceType();
- return mDaemon.execute("resolve", resolveId, name, type, "local.");
+ final String name = service.getServiceName();
+ final String type = service.getServiceType();
+ final Network network = service.getNetwork();
+ final int resolveInterface = getNetworkInterfaceIndex(network);
+ if (network != null && resolveInterface == IFACE_IDX_ANY) {
+ Log.e(TAG, "Interface to resolve service on not found");
+ return false;
+ }
+ return mDaemon.execute("resolve", resolveId, name, type, "local.", resolveInterface);
+ }
+
+ /**
+ * Guess the interface to use to resolve or discover a service on a specific network.
+ *
+ * This is an imperfect guess, as for example the network may be gone or not yet fully
+ * registered. This is fine as failing is correct if the network is gone, and a client
+ * attempting to resolve/discover on a network not yet setup would have a bad time anyway; also
+ * this is to support the legacy mdnsresponder implementation, which historically resolved
+ * services on an unspecified network.
+ */
+ private int getNetworkInterfaceIndex(Network network) {
+ if (network == null) return IFACE_IDX_ANY;
+
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ if (cm == null) {
+ Log.wtf(TAG, "No ConnectivityManager for resolveService");
+ return IFACE_IDX_ANY;
+ }
+ final LinkProperties lp = cm.getLinkProperties(network);
+ if (lp == null) return IFACE_IDX_ANY;
+
+ // Only resolve on non-stacked interfaces
+ final NetworkInterface iface;
+ try {
+ iface = NetworkInterface.getByName(lp.getInterfaceName());
+ } catch (SocketException e) {
+ Log.e(TAG, "Error querying interface", e);
+ return IFACE_IDX_ANY;
+ }
+
+ if (iface == null) {
+ Log.e(TAG, "Interface not found: " + lp.getInterfaceName());
+ return IFACE_IDX_ANY;
+ }
+
+ return iface.getIndex();
}
private boolean stopResolveService(int resolveId) {
return mDaemon.execute("stop-resolve", resolveId);
}
- private boolean getAddrInfo(int resolveId, String hostname) {
- return mDaemon.execute("getaddrinfo", resolveId, hostname);
+ private boolean getAddrInfo(int resolveId, String hostname, String interfaceIdx) {
+ // interfaceIdx is always obtained (as string) from the service resolved callback
+ return mDaemon.execute("getaddrinfo", resolveId, hostname, interfaceIdx);
}
private boolean stopGetAddrInfo(int resolveId) {
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index 17f3455..668d1cb 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -22,8 +22,6 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -470,6 +468,19 @@
}
/**
+ * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+ * format like {@code 0x7fffffff00000000}.
+ */
+ public static int kernelToTag(String string) {
+ int length = string.length();
+ if (length > 10) {
+ return Long.decode(string.substring(0, length - 8)).intValue();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
* Parse statistics from file into given {@link NetworkStats} object. Values
* are expected to monotonically increase since device boot.
*/
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..9f3371b 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -20,9 +20,6 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
-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.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -50,6 +47,9 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -59,8 +59,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 +119,8 @@
import android.provider.Settings.Global;
import android.service.NetworkInterfaceProto;
import android.service.NetworkStatsServiceDumpProto;
+import android.system.ErrnoException;
+import android.system.Os;
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,21 @@
@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;
+ }
+ }
+
+ public TagStatsDeleter getTagStatsDeleter() {
+ return NetworkStatsService::nativeDeleteTagData;
+ }
}
/**
@@ -1077,6 +1107,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);
@@ -1752,7 +1805,10 @@
// Clear kernel stats associated with UID
for (int uid : uids) {
- resetKernelUidStats(uid);
+ final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid);
+ if (ret < 0) {
+ Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret));
+ }
}
}
@@ -2331,4 +2387,12 @@
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
private static native long nativeGetUidStat(int uid, int type);
+
+ // TODO: use BpfNetMaps to delete tag data and remove this.
+ @VisibleForTesting
+ interface TagStatsDeleter {
+ int deleteTagData(int uid);
+ }
+
+ private static native int nativeDeleteTagData(int uid);
}
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..5bba0b1 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -16,15 +16,14 @@
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;
import android.annotation.NonNull;
import android.content.Context;
-import android.telephony.Annotation;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
@@ -57,10 +56,9 @@
*
* @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);
+ void onCollapsedRatTypeChanged(@NonNull String subscriberId, int collapsedRatType);
}
private final Delegate mDelegate;
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/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 1d0ae99..b65e976 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -37,7 +37,7 @@
import android.widget.Button;
import com.android.internal.app.AlertActivity;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import java.io.File;
import java.io.FileInputStream;
@@ -154,8 +154,8 @@
final PackageLite pkg = result.getResult();
params.setAppPackageName(pkg.getPackageName());
params.setInstallLocation(pkg.getInstallLocation());
- params.setSize(
- PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+ params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
+ params.abiOverride));
}
} catch (IOException e) {
Log.e(LOG_TAG,
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/Android.bp b/packages/SettingsLib/Android.bp
index fcf2282..684f4de 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
"SettingsLibButtonPreference",
+ "setupdesign",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index a347345..13f8a37 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,4 +18,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib">
+ <application>
+ <activity
+ android:name="com.android.settingslib.users.AvatarPickerActivity"
+ android:theme="@style/SudThemeGlifV2.DayNight"/>
+ </application>
+
</manifest>
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/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index e51bb45..3eb6ea9 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -42,6 +42,7 @@
@VisibleForTesting
View.OnClickListener mLearnMoreListener;
private CharSequence mContentDescription;
+ private CharSequence mLearnMoreText;
private CharSequence mLearnMoreContentDescription;
private FooterLearnMoreSpan mLearnMoreSpan;
@@ -69,7 +70,12 @@
TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
if (learnMore != null && mLearnMoreListener != null) {
learnMore.setVisibility(View.VISIBLE);
- SpannableString learnMoreText = new SpannableString(learnMore.getText());
+ if (TextUtils.isEmpty(mLearnMoreText)) {
+ mLearnMoreText = learnMore.getText();
+ } else {
+ learnMore.setText(mLearnMoreText);
+ }
+ SpannableString learnMoreText = new SpannableString(mLearnMoreText);
if (mLearnMoreSpan != null) {
learnMoreText.removeSpan(mLearnMoreSpan);
}
@@ -123,6 +129,18 @@
}
/**
+ * Sets the learn more text.
+ *
+ * @param learnMoreText The string of the learn more text.
+ */
+ public void setLearnMoreText(CharSequence learnMoreText) {
+ if (!TextUtils.equals(mLearnMoreText, learnMoreText)) {
+ mLearnMoreText = learnMoreText;
+ notifyChanged();
+ }
+ }
+
+ /**
* To set content description of the learn more text. This can use for talkback
* environment if developer wants to have a customization content.
*
diff --git a/packages/SettingsLib/res/drawable/add_a_photo_circled.xml b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
new file mode 100644
index 0000000..bcfd221
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="?android:attr/colorAccent"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/add_a_photo_circled_padding"
+ android:right="@dimen/add_a_photo_circled_padding"
+ android:top="@dimen/add_a_photo_circled_padding"
+ android:bottom="@dimen/add_a_photo_circled_padding"
+ android:drawable="@drawable/ic_add_a_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
new file mode 100644
index 0000000..97aec74
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_choose_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
new file mode 100644
index 0000000..ccde597
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_selector.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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true">
+ <shape android:shape="oval">
+ <stroke
+ android:color="?android:attr/colorPrimary"
+ android:width="@dimen/avatar_picker_padding"/>
+ </shape>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
new file mode 100644
index 0000000..7033aae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="2dp"
+ android:color="?android:attr/colorPrimary"/>
+ </shape>
+ </item>
+ <item
+ android:left="@dimen/avatar_picker_icon_inset"
+ android:right="@dimen/avatar_picker_icon_inset"
+ android:top="@dimen/avatar_picker_icon_inset"
+ android:bottom="@dimen/avatar_picker_icon_inset"
+ android:drawable="@drawable/ic_avatar_take_photo"/>
+</layer-list>
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
new file mode 100644
index 0000000..0cc54b6
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/>
+</vector>
+
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
similarity index 63%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SettingsLib/res/drawable/ic_add_a_photo.xml
index ff57406..4e35503 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -20,6 +19,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
-</vector>
\ No newline at end of file
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M11.5,17.5q1.875,0 3.188,-1.313Q16,14.876 16,13q0,-1.875 -1.313,-3.188Q13.376,8.5 11.5,8.5q-1.875,0 -3.188,1.313Q7,11.124 7,13q0,1.875 1.313,3.188Q9.624,17.5 11.5,17.5zM3.5,21q-0.825,0 -1.413,-0.587Q1.5,19.825 1.5,19L1.5,7q0,-0.825 0.587,-1.412Q2.675,5 3.5,5h3.15L8.5,3h6v4h-11v12h16v-9h2v9q0,0.825 -0.587,1.413Q20.325,21 19.5,21zM18.5,8L18.5,6h-2L16.5,4h2L18.5,2h2v2h2v2h-2v2zM11.5,13z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
similarity index 69%
rename from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
rename to packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
index ff57406..b85fdc2 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -20,6 +19,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
-</vector>
\ No newline at end of file
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
similarity index 64%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
index ff57406..5c56276 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -20,6 +19,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
-</vector>
\ No newline at end of file
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml
new file mode 100644
index 0000000..c52f664
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/avatar_image"
+ android:layout_height="@dimen/avatar_size_in_picker"
+ android:layout_width="@dimen/avatar_size_in_picker"
+ android:layout_margin="@dimen/avatar_picker_margin"
+ android:layout_gravity="center"
+ android:padding="@dimen/avatar_picker_padding"
+ android:background="@drawable/avatar_selector"/>
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
new file mode 100644
index 0000000..2d40bd0
--- /dev/null
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<com.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/glif_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:icon="@drawable/ic_account_circle_outline"
+ app:sucUsePartnerResource="true"
+ app:sucHeaderText="@string/avatar_picker_title">
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:gravity="center_horizontal"
+ style="@style/SudContentFrame">
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/avatar_grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index f66ff00..6940c39 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -18,24 +18,32 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAligned="false"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:padding="16dp">
- <ImageView
- android:id="@+id/user_photo"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:layout_gravity="bottom"
- android:contentDescription="@string/user_image_photo_selector"
- android:background="@*android:drawable/spinner_background_holo_dark"
- android:scaleType="fitCenter"/>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <ImageView
+ android:id="@+id/user_photo"
+ android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
+ android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
+ android:contentDescription="@string/user_image_photo_selector"
+ android:scaleType="fitCenter"/>
+ <ImageView
+ android:id="@+id/add_a_photo_icon"
+ android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+ android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+ android:src="@drawable/add_a_photo_circled"
+ android:layout_gravity="bottom|right" />
+ </FrameLayout>
<EditText
android:id="@+id/user_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+ android:layout_gravity="center"
android:minWidth="200dp"
android:layout_marginStart="6dp"
android:minHeight="@dimen/min_tap_target_size"
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..6c3e68a 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 b7d370d..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-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
new file mode 100644
index 0000000..cab78d6
--- /dev/null
+++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">112dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
new file mode 100644
index 0000000..4097d05
--- /dev/null
+++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">104dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">3dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
new file mode 100644
index 0000000..764870e
--- /dev/null
+++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">128dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">4dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
new file mode 100644
index 0000000..872b88a
--- /dev/null
+++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">148dp</dimen>
+ <dimen name="avatar_picker_padding">8dp</dimen>
+ <dimen name="avatar_picker_margin">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
new file mode 100644
index 0000000..8403dba
--- /dev/null
+++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <integer name="avatar_picker_columns">4</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+</resources>
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/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 2b5e9cd..29a1831 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -647,4 +647,11 @@
<item>disabled</item>
</array>
+ <!-- Images offered as options in the avatar picker. If populated, the avatar_image_descriptions
+ array must also be populated with a content description for each image. -->
+ <array name="avatar_images"/>
+
+ <!-- Content descriptions for each of the images in the avatar_images array. -->
+ <string-array name="avatar_image_descriptions"/>
+
</resources>
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index b150e01..45253bb 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -28,9 +28,4 @@
<!-- Control whether status bar should distinguish HSPA data icon form UMTS
data icon on devices -->
<bool name="config_hspa_data_distinguishable">false</bool>
-
- <integer-array name="config_supportedDreamComplications">
- </integer-array>
- <integer-array name="config_dreamComplicationsEnabledByDefault">
- </integer-array>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 9bccc3f..e1bd9f7 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -98,4 +98,16 @@
<!-- Minimum width for the popup for updating a user's photo. -->
<dimen name="update_user_photo_popup_min_width">300dp</dimen>
+ <dimen name="add_a_photo_circled_padding">6dp</dimen>
+ <dimen name="user_photo_size_in_user_info_dialog">112dp</dimen>
+ <dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen>
+ <dimen name="user_name_height_in_user_info_dialog">48sp</dimen>
+
+ <integer name="avatar_picker_columns">3</integer>
+ <dimen name="avatar_size_in_picker">96dp</dimen>
+ <dimen name="avatar_picker_padding">6dp</dimen>
+ <dimen name="avatar_picker_margin">2dp</dimen>
+
+ <dimen name="avatar_picker_icon_inset">25dp</dimen>
+
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e4eab4b..0fe869f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1164,6 +1164,9 @@
<!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
<string name="disabled_by_admin_summary_text">Controlled by admin</string>
+ <!-- Summary for settings preference disabled by app ops [CHAR LIMIT=50] -->
+ <string name="disabled_by_app_ops_text">Controlled by Restricted Setting</string>
+
<!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
<string name="disabled">Disabled</string>
<!-- Summary of app trusted to install apps [CHAR LIMIT=45] -->
@@ -1545,4 +1548,22 @@
<!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_no_calling">No calling.</string>
+
+ <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_time">Time</string>
+ <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_date">Date</string>
+ <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_weather">Weather</string>
+ <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_aqi">Air Quality</string>
+ <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_cast_info">Cast Info</string>
+
+ <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
+ <string name="avatar_picker_title">Choose a profile picture</string>
+
+ <!-- Content description for a default user icon. [CHAR LIMIT=NONE] -->
+ <string name="default_user_icon_description">Default user icon</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1e8cb9f..1573edb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -26,14 +26,17 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
@@ -42,6 +45,7 @@
import android.view.MenuItem;
import android.widget.TextView;
+import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
@@ -729,6 +733,26 @@
}
/**
+ * Show restricted setting dialog.
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public static void sendShowRestrictedSettingDialogIntent(Context context,
+ String packageName, int uid) {
+ final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Get restricted settings dialog intent.
+ */
+ private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+ final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ return intent;
+ }
+
+ /**
* Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
* {@link LockPatternUtils} is an internal API not supported by robolectric.
* {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index fc8b587..81146fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -19,6 +19,7 @@
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.content.Context;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
@@ -37,9 +38,14 @@
RestrictedPreferenceHelper mHelper;
public RestrictedPreference(Context context, AttributeSet attrs,
- int defStyleAttr, int defStyleRes) {
+ int defStyleAttr, int defStyleRes, String packageName, int uid) {
super(context, attrs, defStyleAttr, defStyleRes);
- mHelper = new RestrictedPreferenceHelper(context, this, attrs);
+ mHelper = new RestrictedPreferenceHelper(context, this, attrs, packageName, uid);
+ }
+
+ public RestrictedPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ this(context, attrs, defStyleAttr, defStyleRes, null, Process.INVALID_UID);
}
public RestrictedPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -55,6 +61,11 @@
this(context, null);
}
+ public RestrictedPreference(Context context, String packageName, int uid) {
+ this(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle), 0, packageName, uid);
+ }
+
@Override
protected int getSecondTargetResId() {
return R.layout.restricted_icon;
@@ -115,7 +126,21 @@
}
}
+ public void setDisabledByAppOps(boolean disabled) {
+ if (mHelper.setDisabledByAppOps(disabled)) {
+ notifyChanged();
+ }
+ }
+
public boolean isDisabledByAdmin() {
return mHelper.isDisabledByAdmin();
}
+
+ public int getUid() {
+ return mHelper != null ? mHelper.uid : Process.INVALID_UID;
+ }
+
+ public String getPackageName() {
+ return mHelper != null ? mHelper.packageName : null;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 83a6973..f7b2974 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -16,10 +16,14 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -29,6 +33,8 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
+import com.android.internal.util.Preconditions;
+
/**
* Helper class for managing settings preferences that can be disabled
* by device admins via user restrictions.
@@ -36,16 +42,22 @@
public class RestrictedPreferenceHelper {
private final Context mContext;
private final Preference mPreference;
+ final String packageName;
+ final int uid;
private boolean mDisabledByAdmin;
private EnforcedAdmin mEnforcedAdmin;
private String mAttrUserRestriction = null;
- private boolean mUseAdminDisabledSummary = false;
+ private boolean mDisabledSummary = false;
+
+ private boolean mDisabledByAppOps;
public RestrictedPreferenceHelper(Context context, Preference preference,
- AttributeSet attrs) {
+ AttributeSet attrs, String packageName, int uid) {
mContext = context;
mPreference = preference;
+ this.packageName = packageName;
+ this.uid = uid;
if (attrs != null) {
final TypedArray attributes = context.obtainStyledAttributes(attrs,
@@ -71,27 +83,37 @@
final TypedValue useAdminDisabledSummary =
attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary);
if (useAdminDisabledSummary != null) {
- mUseAdminDisabledSummary =
+ mDisabledSummary =
(useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN
&& useAdminDisabledSummary.data != 0);
}
}
}
+ public RestrictedPreferenceHelper(Context context, Preference preference,
+ AttributeSet attrs) {
+ this(context, preference, attrs, null, android.os.Process.INVALID_UID);
+ }
+
/**
* Modify PreferenceViewHolder to add padlock if restriction is disabled.
*/
public void onBindViewHolder(PreferenceViewHolder holder) {
- if (mDisabledByAdmin) {
+ if (mDisabledByAdmin || mDisabledByAppOps) {
holder.itemView.setEnabled(true);
}
- if (mUseAdminDisabledSummary) {
+ if (mDisabledSummary) {
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView != null) {
- final CharSequence disabledText = summaryView.getContext().getText(
- R.string.disabled_by_admin_summary_text);
+ final CharSequence disabledText = mContext
+ .getSystemService(DevicePolicyManager.class)
+ .getString(CONTROLLED_BY_ADMIN_SUMMARY,
+ () -> summaryView.getContext().getString(
+ R.string.disabled_by_admin_summary_text));
if (mDisabledByAdmin) {
summaryView.setText(disabledText);
+ } else if (mDisabledByAppOps) {
+ summaryView.setText(R.string.disabled_by_app_ops_text);
} else if (TextUtils.equals(disabledText, summaryView.getText())) {
// It's previously set to disabled text, clear it.
summaryView.setText(null);
@@ -101,7 +123,7 @@
}
public void useAdminDisabledSummary(boolean useSummary) {
- mUseAdminDisabledSummary = useSummary;
+ mDisabledSummary = useSummary;
}
/**
@@ -109,11 +131,19 @@
*
* @return true if the method handled the click.
*/
+ @SuppressWarnings("NewApi")
public boolean performClick() {
if (mDisabledByAdmin) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
return true;
}
+ if (mDisabledByAppOps) {
+ Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU,
+ "Build SDK version needs >= T");
+ RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
+ uid);
+ return true;
+ }
return false;
}
@@ -166,14 +196,34 @@
changed = true;
}
- if (!(mPreference instanceof RestrictedTopLevelPreference)) {
- mPreference.setEnabled(!disabled);
+ updateDisabledState();
+
+ return changed;
+ }
+
+ public boolean setDisabledByAppOps(boolean disabled) {
+ boolean changed = false;
+ if (mDisabledByAppOps != disabled) {
+ mDisabledByAppOps = disabled;
+ changed = true;
}
+ updateDisabledState();
+
return changed;
}
public boolean isDisabledByAdmin() {
return mDisabledByAdmin;
}
+
+ public boolean isDisabledByAppOps() {
+ return mDisabledByAppOps;
+ }
+
+ private void updateDisabledState() {
+ if (!(mPreference instanceof RestrictedTopLevelPreference)) {
+ mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d73e45e..883e080 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,10 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
+
import android.annotation.ColorInt;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -124,7 +127,8 @@
String name = info != null ? info.name : null;
if (info.isManagedProfile()) {
// We use predefined values for managed profiles
- return context.getString(R.string.managed_user_title);
+ return context.getSystemService(DevicePolicyManager.class).getString(
+ WORK_PROFILE_USER_LABEL, () -> context.getString(R.string.managed_user_title));
} else if (info.isGuest()) {
name = context.getString(R.string.user_guest);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java
new file mode 100644
index 0000000..9dfc8ea
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.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.settingslib.applications;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache app icon for management.
+ */
+public class AppIconCacheManager {
+ private static final String TAG = "AppIconCacheManager";
+ private static final float CACHE_RATIO = 0.1f;
+ private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
+ private static final String DELIMITER = ":";
+ private static AppIconCacheManager sAppIconCacheManager;
+ private final LruCache<String, Drawable> mDrawableCache;
+
+ private AppIconCacheManager() {
+ mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) {
+ @Override
+ protected int sizeOf(String key, Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024;
+ }
+ // Rough estimate each pixel will use 4 bytes by default.
+ return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024;
+ }
+ };
+ }
+
+ /**
+ * Get an {@link AppIconCacheManager} instance.
+ */
+ public static synchronized AppIconCacheManager getInstance() {
+ if (sAppIconCacheManager == null) {
+ sAppIconCacheManager = new AppIconCacheManager();
+ }
+ return sAppIconCacheManager;
+ }
+
+ /**
+ * Put app icon to cache
+ *
+ * @param packageName of icon
+ * @param uid of packageName
+ * @param drawable app icon
+ */
+ public void put(String packageName, int uid, Drawable drawable) {
+ final String key = getKey(packageName, uid);
+ if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0
+ || drawable.getIntrinsicWidth() < 0) {
+ Log.w(TAG, "Invalid key or drawable.");
+ return;
+ }
+ mDrawableCache.put(key, drawable);
+ }
+
+ /**
+ * Get app icon from cache.
+ *
+ * @param packageName of icon
+ * @param uid of packageName
+ * @return app icon
+ */
+ public Drawable get(String packageName, int uid) {
+ final String key = getKey(packageName, uid);
+ if (key == null) {
+ Log.w(TAG, "Invalid key with package or uid.");
+ return null;
+ }
+ final Drawable cachedDrawable = mDrawableCache.get(key);
+ return cachedDrawable != null ? cachedDrawable.mutate() : null;
+ }
+
+ /**
+ * Release cache.
+ */
+ public static void release() {
+ if (sAppIconCacheManager != null) {
+ sAppIconCacheManager.mDrawableCache.evictAll();
+ }
+ }
+
+ private static String getKey(String packageName, int uid) {
+ if (packageName == null || uid < 0) {
+ return null;
+ }
+ return packageName + DELIMITER + UserHandle.getUserId(uid);
+ }
+
+ private static int getMaxCacheInKb() {
+ return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index a5da8b6..cc4fef8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
import android.hardware.usb.IUsbManager;
import android.net.Uri;
import android.os.Environment;
@@ -35,7 +36,9 @@
import android.util.Log;
import com.android.settingslib.R;
+import com.android.settingslib.Utils;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
@@ -212,4 +215,82 @@
UserHandle.myUserId());
return TextUtils.equals(packageName, defaultBrowserPackage);
}
+
+ /**
+ * Get the app icon by app entry.
+ *
+ * @param context caller's context
+ * @param appEntry AppEntry of ApplicationsState
+ * @return app icon of the app entry
+ */
+ public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) {
+ if (appEntry == null || appEntry.info == null) {
+ return null;
+ }
+
+ final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance();
+ final String packageName = appEntry.info.packageName;
+ final int uid = appEntry.info.uid;
+
+ Drawable icon = appIconCacheManager.get(packageName, uid);
+ if (icon == null) {
+ if (appEntry.apkFile != null && appEntry.apkFile.exists()) {
+ icon = Utils.getBadgedIcon(context, appEntry.info);
+ appIconCacheManager.put(packageName, uid, icon);
+ } else {
+ setAppEntryMounted(appEntry, /* mounted= */ false);
+ icon = context.getDrawable(
+ com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+ }
+ } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) {
+ // If the app wasn't mounted but is now mounted, reload its icon.
+ setAppEntryMounted(appEntry, /* mounted= */ true);
+ icon = Utils.getBadgedIcon(context, appEntry.info);
+ appIconCacheManager.put(packageName, uid, icon);
+ }
+
+ return icon;
+ }
+
+ /**
+ * Get the app icon from cache by app entry.
+ *
+ * @param appEntry AppEntry of ApplicationsState
+ * @return app icon of the app entry
+ */
+ public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) {
+ return appEntry == null || appEntry.info == null ? null
+ : AppIconCacheManager.getInstance().get(
+ appEntry.info.packageName,
+ appEntry.info.uid);
+ }
+
+ /**
+ * Preload the top N icons of app entry list.
+ *
+ * @param context caller's context
+ * @param appEntries AppEntry list of ApplicationsState
+ * @param number the number of Top N icons of the appEntries
+ */
+ public static void preloadTopIcons(Context context,
+ ArrayList<ApplicationsState.AppEntry> appEntries, int number) {
+ if (appEntries == null || appEntries.isEmpty() || number <= 0) {
+ return;
+ }
+
+ for (int i = 0; i < Math.min(appEntries.size(), number); i++) {
+ final ApplicationsState.AppEntry entry = appEntries.get(i);
+ ThreadUtils.postOnBackgroundThread(() -> {
+ getIcon(context, entry);
+ });
+ }
+ }
+
+ private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) {
+ if (appEntry.mounted != mounted) {
+ synchronized (appEntry) {
+ appEntry.mounted = mounted;
+ }
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f046f06..fdb0607 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -95,6 +95,7 @@
private static final Object sLock = new Object();
private static final Pattern REMOVE_DIACRITICALS_PATTERN
= Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+ private static final String SETTING_PKG = "com.android.settings";
@VisibleForTesting
static ApplicationsState sInstance;
@@ -492,6 +493,9 @@
return null;
}
+ /**
+ * Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied.
+ */
public void ensureIcon(AppEntry entry) {
if (entry.icon != null) {
return;
@@ -758,6 +762,10 @@
return null;
}
+ private static boolean isAppIconCacheEnabled(Context context) {
+ return SETTING_PKG.equals(context.getPackageName());
+ }
+
void rebuildActiveSessions() {
synchronized (mEntriesMap) {
if (!mSessionsChanged) {
@@ -806,6 +814,11 @@
} else {
mHasLifecycle = false;
}
+
+ if (isAppIconCacheEnabled(mContext)) {
+ // Skip the preloading all icons step to save memory usage.
+ mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS;
+ }
}
@SessionFlags
@@ -814,7 +827,12 @@
}
public void setSessionFlags(@SessionFlags int flags) {
- mFlags = flags;
+ if (isAppIconCacheEnabled(mContext)) {
+ // Skip the preloading all icons step to save memory usage.
+ mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS;
+ } else {
+ mFlags = flags;
+ }
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -1576,6 +1594,10 @@
// Need to synchronize on 'this' for the following.
public ApplicationInfo info;
+ /**
+ * Starting Android T, this field will not be used if {@link AppIconCacheManager} is
+ * applied.
+ */
public Drawable icon;
public String sizeStr;
public String internalSizeStr;
@@ -1596,15 +1618,11 @@
this.size = SIZE_UNKNOWN;
this.sizeStale = true;
ensureLabel(context);
- // Speed up the cache of the icon and label description if they haven't been created.
- ThreadUtils.postOnBackgroundThread(() -> {
- if (this.icon == null) {
- this.ensureIconLocked(context);
- }
- if (this.labelDescription == null) {
- this.ensureLabelDescriptionLocked(context);
- }
- });
+ // Speed up the cache of the label description if they haven't been created.
+ if (this.labelDescription == null) {
+ ThreadUtils.postOnBackgroundThread(
+ () -> this.ensureLabelDescriptionLocked(context));
+ }
}
public void ensureLabel(Context context) {
@@ -1620,7 +1638,15 @@
}
}
+ /**
+ * Starting Android T, this method will not be used if {@link AppIconCacheManager} is
+ * applied.
+ */
boolean ensureIconLocked(Context context) {
+ if (isAppIconCacheEnabled(context)) {
+ return false;
+ }
+
if (this.icon == null) {
if (this.apkFile.exists()) {
this.icon = Utils.getBadgedIcon(context, info);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 99e3160..df19c67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -173,8 +173,10 @@
}
public BluetoothDevice getActiveDevice() {
- if (mService == null) return null;
- return mService.getActiveDevice();
+ if (mBluetoothAdapter == null) return null;
+ final List<BluetoothDevice> activeDevices = mBluetoothAdapter
+ .getActiveDevices(BluetoothProfile.A2DP);
+ return (activeDevices.size() > 0) ? activeDevices.get(0) : null;
}
@Override
@@ -221,7 +223,7 @@
}
public boolean supportsHighQualityAudio(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
if (bluetoothDevice == null) {
return false;
}
@@ -234,7 +236,7 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
if (bluetoothDevice == null) {
return false;
}
@@ -260,7 +262,7 @@
}
public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
- BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
if (bluetoothDevice == null) {
return;
}
@@ -286,7 +288,7 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice();
int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
if (bluetoothDevice == null || !supportsHighQualityAudio(device)
|| getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
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/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index b11bbde..7e5c124 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -132,10 +132,12 @@
}
public BluetoothDevice getActiveDevice() {
- if (mService == null) {
+ if (mBluetoothAdapter == null) {
return null;
}
- return mService.getActiveDevice();
+ final List<BluetoothDevice> activeDevices = mBluetoothAdapter
+ .getActiveDevices(BluetoothProfile.HEADSET);
+ return (activeDevices.size() > 0) ? activeDevices.get(0) : null;
}
public int getAudioState(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index dc109ca..6f2d4de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -173,8 +173,10 @@
}
public List<BluetoothDevice> getActiveDevices() {
- if (mService == null) return new ArrayList<>();
- return mService.getActiveDevices();
+ if (mBluetoothAdapter == null) {
+ return new ArrayList<>();
+ }
+ return mBluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 209507a..e203cba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,12 +21,12 @@
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
@@ -144,20 +144,20 @@
* @hide
*/
public boolean connect(BluetoothDevice device) {
- if (mService == null) {
- return false;
- }
- return mService.connect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
/*
* @hide
*/
public boolean disconnect(BluetoothDevice device) {
- if (mService == null) {
- return false;
- }
- return mService.disconnect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -177,10 +177,10 @@
}
public List<BluetoothDevice> getActiveDevices() {
- if (mService == null) {
+ if (mBluetoothAdapter == null) {
return new ArrayList<>();
}
- return mService.getActiveDevices();
+ return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
}
@Override
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..d179b82 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -34,6 +34,7 @@
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -50,6 +51,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -116,6 +118,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,14 +146,18 @@
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))
- .boxed()
- .collect(Collectors.toSet());
+ mSupportedComplications = Arrays.stream(resources.getIntArray(
+ com.android.internal.R.array.config_supportedDreamComplications))
+ .boxed()
+ .collect(Collectors.toSet());
- mDefaultEnabledComplications = Arrays.stream(
- resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault))
+ mDefaultEnabledComplications = Arrays.stream(resources.getIntArray(
+ com.android.internal.R.array.config_dreamComplicationsEnabledByDefault))
.boxed()
// A complication can only be enabled by default if it is also supported.
.filter(mSupportedComplications::contains)
@@ -166,14 +173,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);
@@ -285,6 +294,11 @@
}
}
+ /** Returns whether a particular complication is enabled */
+ public boolean isComplicationEnabled(@ComplicationType int complication) {
+ return getEnabledComplications().contains(complication);
+ }
+
/** Gets all complications which have been enabled by the user. */
public Set<Integer> getEnabledComplications() {
final String enabledComplications = Settings.Secure.getString(
@@ -324,6 +338,35 @@
convertToString(enabledComplications));
}
+ /**
+ * Gets the title of a particular complication type to be displayed to the user. If there
+ * is no title, null is returned.
+ */
+ @Nullable
+ public CharSequence getComplicationTitle(@ComplicationType int complicationType) {
+ int res = 0;
+ switch (complicationType) {
+ case COMPLICATION_TYPE_TIME:
+ res = R.string.dream_complication_title_time;
+ break;
+ case COMPLICATION_TYPE_DATE:
+ res = R.string.dream_complication_title_date;
+ break;
+ case COMPLICATION_TYPE_WEATHER:
+ res = R.string.dream_complication_title_weather;
+ break;
+ case COMPLICATION_TYPE_AIR_QUALITY:
+ res = R.string.dream_complication_title_aqi;
+ break;
+ case COMPLICATION_TYPE_CAST_INFO:
+ res = R.string.dream_complication_title_cast_info;
+ break;
+ default:
+ return null;
+ }
+ return mContext.getString(res);
+ }
+
private static String convertToString(Set<Integer> set) {
return set.stream()
.map(String::valueOf)
@@ -331,6 +374,9 @@
}
private static Set<Integer> parseFromString(String string) {
+ if (TextUtils.isEmpty(string)) {
+ return new HashSet<>();
+ }
return Arrays.stream(string.split(","))
.map(Integer::parseInt)
.collect(Collectors.toSet());
@@ -482,7 +528,18 @@
if (flattenedString.indexOf('/') < 0) {
flattenedString = serviceInfo.packageName + "/" + flattenedString;
}
- return ComponentName.unflattenFromString(flattenedString);
+
+ ComponentName cn = ComponentName.unflattenFromString(flattenedString);
+
+ if (cn == null) return null;
+ if (!cn.getPackageName().equals(serviceInfo.packageName)) {
+ Log.w(TAG,
+ "Inconsistent package name in component: " + cn.getPackageName()
+ + ", should be: " + serviceInfo.packageName);
+ return null;
+ }
+
+ return cn;
}
private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
new file mode 100644
index 0000000..61b8911
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.util.EventLog;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class AvatarPhotoController {
+ private static final String TAG = "AvatarPhotoController";
+
+ private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+ // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+ // so we need a default photo size
+ private static final int DEFAULT_PHOTO_SIZE = 500;
+
+ private static final String IMAGES_DIR = "multi_user";
+ private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+ private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+
+ private final int mPhotoSize;
+
+ private final AvatarPickerActivity mActivity;
+ private final String mFileAuthority;
+
+ private final File mImagesDir;
+ private final Uri mCropPictureUri;
+ private final Uri mTakePictureUri;
+
+ AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) {
+ mActivity = activity;
+ mFileAuthority = fileAuthority;
+
+ mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir.mkdir();
+ mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = getPhotoSize(activity);
+ }
+
+ /**
+ * Handles activity result from containing activity/fragment after a take/choose/crop photo
+ * action result is received.
+ */
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
+ return false;
+ }
+ final Uri pictureUri = data != null && data.getData() != null
+ ? data.getData() : mTakePictureUri;
+
+ // Check if the result is a content uri
+ if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
+ Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
+ EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
+ return false;
+ }
+
+ switch (requestCode) {
+ case REQUEST_CODE_CROP_PHOTO:
+ mActivity.returnUriResult(pictureUri);
+ return true;
+ case REQUEST_CODE_TAKE_PHOTO:
+ case REQUEST_CODE_CHOOSE_PHOTO:
+ if (mTakePictureUri.equals(pictureUri)) {
+ if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+ cropPhoto();
+ } else {
+ onPhotoNotCropped(pictureUri);
+ }
+ } else {
+ copyAndCropPhoto(pictureUri);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+ appendOutputExtra(intent, mTakePictureUri);
+ mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ void choosePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null);
+ intent.setType("image/*");
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ private void copyAndCropPhoto(final Uri pictureUri) {
+ // TODO: Replace AsyncTask
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ final ContentResolver cr = mActivity.getContentResolver();
+ try (InputStream in = cr.openInputStream(pictureUri);
+ OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy photo", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+ cropPhoto();
+ }
+ }
+ }.execute();
+ }
+
+ private void cropPhoto() {
+ // TODO: Use a public intent, when there is one.
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(mTakePictureUri, "image/*");
+ appendOutputExtra(intent, mCropPictureUri);
+ appendCropExtras(intent);
+ if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ } else {
+ onPhotoNotCropped(mTakePictureUri);
+ }
+ }
+
+ private void appendOutputExtra(Intent intent, Uri pictureUri) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+ }
+
+ private void appendCropExtras(Intent intent) {
+ intent.putExtra("crop", "true");
+ intent.putExtra("scale", true);
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoSize);
+ intent.putExtra("outputY", mPhotoSize);
+ }
+
+ private void onPhotoNotCropped(final Uri data) {
+ // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ // Scale and crop to a square aspect ratio
+ Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(croppedImage);
+ Bitmap fullImage;
+ try {
+ InputStream imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ fullImage = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ return null;
+ }
+ if (fullImage != null) {
+ int rotation = getRotation(mActivity, data);
+ final int squareSize = Math.min(fullImage.getWidth(),
+ fullImage.getHeight());
+ final int left = (fullImage.getWidth() - squareSize) / 2;
+ final int top = (fullImage.getHeight() - squareSize) / 2;
+
+ Matrix matrix = new Matrix();
+ RectF rectSource = new RectF(left, top,
+ left + squareSize, top + squareSize);
+ RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+ matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+ matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+ canvas.drawBitmap(fullImage, matrix, new Paint());
+ return croppedImage;
+ } else {
+ // Bah! Got nothin.
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
+ mActivity.returnUriResult(mCropPictureUri);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ /**
+ * Reads the image's exif data and determines the rotation degree needed to display the image
+ * in portrait mode.
+ */
+ private int getRotation(Context context, Uri selectedImage) {
+ int rotation = -1;
+ try {
+ InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ ExifInterface exif = new ExifInterface(imageStream);
+ rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+ } catch (IOException exception) {
+ Log.e(TAG, "Error while getting rotation", exception);
+ }
+
+ switch (rotation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ private void saveBitmapToFile(Bitmap bitmap, File file) {
+ try {
+ OutputStream os = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot create temp file", e);
+ }
+ }
+
+ private static int getPhotoSize(Context context) {
+ try (Cursor cursor = context.getContentResolver().query(
+ ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+ if (cursor != null) {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } else {
+ return DEFAULT_PHOTO_SIZE;
+ }
+ }
+ }
+
+ private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+ final File fullPath = new File(mImagesDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
new file mode 100644
index 0000000..93be66a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Activity to allow the user to choose a user profile picture.
+ *
+ * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ * <p>If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ * <p>This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
+ */
+public class AvatarPickerActivity extends Activity {
+
+ static final String EXTRA_FILE_AUTHORITY = "file_authority";
+ static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_SELECTED_POSITION = "selected_position";
+
+ private boolean mWaitingForActivityResult;
+
+ private FooterButton mDoneButton;
+ private AvatarAdapter mAdapter;
+
+ private AvatarPhotoController mAvatarPhotoController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeHelper.trySetDynamicColor(this);
+ setContentView(R.layout.avatar_picker);
+ setUpButtons();
+
+ RecyclerView recyclerView = findViewById(R.id.avatar_grid);
+ mAdapter = new AvatarAdapter();
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new GridLayoutManager(this,
+ getResources().getInteger(R.integer.avatar_picker_columns)));
+
+ restoreState(savedInstanceState);
+
+ mAvatarPhotoController = new AvatarPhotoController(
+ this, mWaitingForActivityResult, getFileAuthority());
+ }
+
+ private void setUpButtons() {
+ GlifLayout glifLayout = findViewById(R.id.glif_layout);
+ FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class);
+
+ FooterButton secondaryButton =
+ new FooterButton.Builder(this)
+ .setText("Cancel")
+ .setListener(view -> cancel())
+ .build();
+
+ mDoneButton =
+ new FooterButton.Builder(this)
+ .setText("Done")
+ .setListener(view -> mAdapter.returnSelectionResult())
+ .build();
+ mDoneButton.setEnabled(false);
+
+ mixin.setSecondaryButton(secondaryButton);
+ mixin.setPrimaryButton(mDoneButton);
+ }
+
+ private String getFileAuthority() {
+ String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY);
+ if (authority == null) {
+ throw new IllegalStateException("File authority must be provided");
+ }
+ return authority;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+ mAvatarPhotoController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+ outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition);
+ super.onSaveInstanceState(outState);
+ }
+
+ private void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+ mAdapter.mSelectedPosition =
+ savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE);
+ }
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mWaitingForActivityResult = true;
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ void returnUriResult(Uri uri) {
+ Intent resultData = new Intent();
+ resultData.setData(uri);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ void returnColorResult(int color) {
+ Intent resultData = new Intent();
+ resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ private void cancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> {
+
+ private static final int NONE = -1;
+
+ private final int mTakePhotoPosition;
+ private final int mChoosePhotoPosition;
+ private final int mPreselectedImageStartPosition;
+
+ private final List<Drawable> mImageDrawables;
+ private final List<String> mImageDescriptions;
+ private final TypedArray mPreselectedImages;
+ private final int[] mUserIconColors;
+ private int mSelectedPosition = NONE;
+
+ AvatarAdapter() {
+ final boolean canTakePhoto =
+ PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this);
+ final boolean canChoosePhoto =
+ PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this);
+ mTakePhotoPosition = (canTakePhoto ? 0 : NONE);
+ mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
+ mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
+
+ mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+ mUserIconColors = UserIcons.getUserIconColors(getResources());
+ mImageDrawables = buildDrawableList();
+ mImageDescriptions = buildDescriptionsList();
+ }
+
+ @NonNull
+ @Override
+ public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false);
+ return new AvatarViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) {
+ if (position == mTakePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled));
+ viewHolder.setContentDescription(getString(R.string.user_image_take_photo));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto());
+
+ } else if (position == mChoosePhotoPosition) {
+ viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled));
+ viewHolder.setContentDescription(getString(R.string.user_image_choose_photo));
+ viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
+
+ } else if (position >= mPreselectedImageStartPosition) {
+ int index = indexFromPosition(position);
+ viewHolder.setSelected(position == mSelectedPosition);
+ viewHolder.setDrawable(mImageDrawables.get(index));
+ if (mImageDescriptions != null) {
+ viewHolder.setContentDescription(mImageDescriptions.get(index));
+ } else {
+ viewHolder.setContentDescription(
+ getString(R.string.default_user_icon_description));
+ }
+ viewHolder.setClickListener(view -> {
+ if (mSelectedPosition == position) {
+ deselect(position);
+ } else {
+ select(position);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPreselectedImageStartPosition + mImageDrawables.size();
+ }
+
+ private List<Drawable> buildDrawableList() {
+ List<Drawable> result = new ArrayList<>();
+
+ for (int i = 0; i < mPreselectedImages.length(); i++) {
+ Drawable drawable = mPreselectedImages.getDrawable(i);
+ if (drawable instanceof BitmapDrawable) {
+ result.add(circularDrawableFrom((BitmapDrawable) drawable));
+ } else {
+ throw new IllegalStateException("Avatar drawables must be bitmaps");
+ }
+ }
+ if (!result.isEmpty()) {
+ return result;
+ }
+
+ // No preselected images. Use tinted default icon.
+ for (int i = 0; i < mUserIconColors.length; i++) {
+ result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+ }
+ return result;
+ }
+
+ private List<String> buildDescriptionsList() {
+ if (mPreselectedImages.length() > 0) {
+ return Arrays.asList(
+ getResources().getStringArray(R.array.avatar_image_descriptions));
+ }
+
+ return null;
+ }
+
+ private Drawable circularDrawableFrom(BitmapDrawable drawable) {
+ Bitmap bitmap = drawable.getBitmap();
+
+ RoundedBitmapDrawable roundedBitmapDrawable =
+ RoundedBitmapDrawableFactory.create(getResources(), bitmap);
+ roundedBitmapDrawable.setCircular(true);
+
+ return roundedBitmapDrawable;
+ }
+
+ private int indexFromPosition(int position) {
+ return position - mPreselectedImageStartPosition;
+ }
+
+ private void select(int position) {
+ final int oldSelection = mSelectedPosition;
+ mSelectedPosition = position;
+ notifyItemChanged(position);
+ if (oldSelection != NONE) {
+ notifyItemChanged(oldSelection);
+ } else {
+ mDoneButton.setEnabled(true);
+ }
+ }
+
+ private void deselect(int position) {
+ mSelectedPosition = NONE;
+ notifyItemChanged(position);
+ mDoneButton.setEnabled(false);
+ }
+
+ private void returnSelectionResult() {
+ int index = indexFromPosition(mSelectedPosition);
+ if (mPreselectedImages.length() > 0) {
+ int resourceId = mPreselectedImages.getResourceId(index, -1);
+ if (resourceId == -1) {
+ throw new IllegalStateException("Preselected avatar images must be resources.");
+ }
+ returnUriResult(uriForResourceId(resourceId));
+ } else {
+ returnColorResult(
+ mUserIconColors[index]);
+ }
+ }
+
+ private Uri uriForResourceId(int resourceId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getResources().getResourcePackageName(resourceId))
+ .appendPath(getResources().getResourceTypeName(resourceId))
+ .appendPath(getResources().getResourceEntryName(resourceId))
+ .build();
+ }
+ }
+
+ private static class AvatarViewHolder extends RecyclerView.ViewHolder {
+ private final ImageView mImageView;
+
+ AvatarViewHolder(View view) {
+ super(view);
+ mImageView = view.findViewById(R.id.avatar_image);
+ }
+
+ public void setDrawable(Drawable drawable) {
+ mImageView.setImageDrawable(drawable);
+ }
+
+ public void setContentDescription(String desc) {
+ mImageView.setContentDescription(desc);
+ }
+
+ public void setClickListener(View.OnClickListener listener) {
+ mImageView.setOnClickListener(listener);
+ }
+
+ public void setSelected(boolean selected) {
+ mImageView.setSelected(selected);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 5859953..80ee86f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -36,6 +37,8 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
import java.io.File;
@@ -139,13 +142,20 @@
Drawable userIcon = getUserIcon(activity, defaultUserIcon);
userPhotoView.setImageDrawable(userIcon);
- if (canChangePhoto(activity)) {
- mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
- userPhotoView);
+ if (isChangePhotoRestrictedByBase(activity)) {
+ // some users can't change their photos so we need to remove the suggestive icon
+ content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
} else {
- // some users can't change their photos so we need to remove suggestive
- // background from the photoView
- userPhotoView.setBackground(null);
+ RestrictedLockUtils.EnforcedAdmin adminRestriction =
+ getChangePhotoAdminRestriction(activity);
+ if (adminRestriction != null) {
+ userPhotoView.setOnClickListener(view ->
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ activity, adminRestriction));
+ } else {
+ mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+ userPhotoView);
+ }
}
mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
@@ -204,16 +214,21 @@
}
@VisibleForTesting
- boolean canChangePhoto(Context context) {
- return (PhotoCapabilityUtils.canCropPhoto(context)
- && PhotoCapabilityUtils.canChoosePhoto(context))
- || PhotoCapabilityUtils.canTakePhoto(context);
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+ return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
}
@VisibleForTesting
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+ mSavedPhoto, mFileAuthority);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f9584a3..f8bb38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,46 +16,21 @@
package com.android.settingslib.users;
+import android.annotation.NonNull;
import android.app.Activity;
-import android.content.ClipData;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.media.ExifInterface;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.StrictMode;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.provider.MediaStore;
-import android.util.EventLog;
import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import android.widget.ImageView;
-import android.widget.ListPopupWindow;
-import android.widget.TextView;
-import androidx.core.content.FileProvider;
-
+import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.drawable.CircleFramedDrawable;
-
-import libcore.io.Streams;
+import com.android.settingslib.utils.ThreadUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -63,8 +38,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
/**
* This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -75,45 +49,30 @@
// It seems that this class generates custom request codes and they may
// collide with ours, these values are very unlikely to have a conflict.
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
- private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
- private static final int REQUEST_CODE_CROP_PHOTO = 1003;
- // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
- // so we need a default photo size
- private static final int DEFAULT_PHOTO_SIZE = 500;
+ private static final int REQUEST_CODE_PICK_AVATAR = 1004;
private static final String IMAGES_DIR = "multi_user";
- private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
- private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
- private final int mPhotoSize;
-
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
private final String mFileAuthority;
private final File mImagesDir;
- private final Uri mCropPictureUri;
- private final Uri mTakePictureUri;
-
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
- ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+ ImageView view, Bitmap bitmap, String fileAuthority) {
mActivity = activity;
mActivityStarter = activityStarter;
- mImageView = view;
mFileAuthority = fileAuthority;
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
- mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(activity);
- mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+ mImageView = view;
+ mImageView.setOnClickListener(v -> showAvatarPicker());
mNewUserPhotoBitmap = bitmap;
}
@@ -125,32 +84,19 @@
if (resultCode != Activity.RESULT_OK) {
return false;
}
- final Uri pictureUri = data != null && data.getData() != null
- ? data.getData() : mTakePictureUri;
- // Check if the result is a content uri
- if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) {
- Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme());
- EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath());
- return false;
- }
+ if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+ if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+ int tintColor =
+ data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+ onDefaultIconSelected(tintColor);
+ return true;
+ }
+ if (data.getData() != null) {
+ onPhotoCropped(data.getData());
+ return true;
+ }
- switch (requestCode) {
- case REQUEST_CODE_CROP_PHOTO:
- onPhotoCropped(pictureUri);
- return true;
- case REQUEST_CODE_TAKE_PHOTO:
- case REQUEST_CODE_CHOOSE_PHOTO:
- if (mTakePictureUri.equals(pictureUri)) {
- if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
- cropPhoto();
- } else {
- onPhotoNotCropped(pictureUri);
- }
- } else {
- copyAndCropPhoto(pictureUri);
- }
- return true;
}
return false;
}
@@ -159,224 +105,60 @@
return mNewUserPhotoDrawable;
}
- private void showUpdatePhotoPopup() {
- final Context context = mImageView.getContext();
- final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
- final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
-
- if (!canTakePhoto && !canChoosePhoto) {
- return;
- }
-
- final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
-
- if (canTakePhoto) {
- final String title = context.getString(R.string.user_image_take_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::takePhoto));
- }
-
- if (canChoosePhoto) {
- final String title = context.getString(R.string.user_image_choose_photo);
- items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
- this::choosePhoto));
- }
-
- final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
- listPopupWindow.setAnchorView(mImageView);
- listPopupWindow.setModal(true);
- listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
- listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
-
- final int width = Math.max(mImageView.getWidth(), context.getResources()
- .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
- listPopupWindow.setWidth(width);
- listPopupWindow.setDropDownGravity(Gravity.START);
-
- listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
- listPopupWindow.dismiss();
- final RestrictedMenuItem item =
- (RestrictedMenuItem) parent.getAdapter().getItem(position);
- item.doAction();
- });
-
- listPopupWindow.show();
+ private void showAvatarPicker() {
+ Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
- private void takePhoto() {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
- }
+ private void onDefaultIconSelected(int tintColor) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Drawable drawable =
+ UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+ Bitmap bitmap = convertToBitmap(drawable,
+ (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
- private void choosePhoto() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- appendOutputExtra(intent, mTakePictureUri);
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
- }
-
- private void copyAndCropPhoto(final Uri pictureUri) {
- // TODO: Replace AsyncTask
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- final ContentResolver cr = mActivity.getContentResolver();
- try (InputStream in = cr.openInputStream(pictureUri);
- OutputStream out = cr.openOutputStream(mTakePictureUri)) {
- Streams.copy(in, out);
- } catch (IOException e) {
- Log.w(TAG, "Failed to copy photo", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
- cropPhoto();
- }
- }
- }.execute();
- }
-
- private void cropPhoto() {
- // TODO: Use a public intent, when there is one.
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(mTakePictureUri, "image/*");
- appendOutputExtra(intent, mCropPictureUri);
- appendCropExtras(intent);
- if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
- try {
- StrictMode.disableDeathOnFileUriExposure();
- mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- } else {
- onPhotoNotCropped(mTakePictureUri);
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error processing default icon", e);
}
}
- private void appendOutputExtra(Intent intent, Uri pictureUri) {
- intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
- }
-
- private void appendCropExtras(Intent intent) {
- intent.putExtra("crop", "true");
- intent.putExtra("scale", true);
- intent.putExtra("scaleUpIfNeeded", true);
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoSize);
- intent.putExtra("outputY", mPhotoSize);
+ private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, size, size);
+ icon.draw(canvas);
+ return bitmap;
}
private void onPhotoCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- InputStream imageStream = null;
- try {
- imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- return BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- Log.w(TAG, "Cannot find image file", fe);
- return null;
- } finally {
- if (imageStream != null) {
- try {
- imageStream.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Cannot close image stream", ioe);
- }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ InputStream imageStream = null;
+ Bitmap bitmap = null;
+ try {
+ imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ bitmap = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
}
}
}
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
-
+ if (bitmap != null) {
+ Bitmap finalBitmap = bitmap;
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- private void onPhotoNotCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- // Scale and crop to a square aspect ratio
- Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
- Config.ARGB_8888);
- Canvas canvas = new Canvas(croppedImage);
- Bitmap fullImage;
- try {
- InputStream imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- fullImage = BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- return null;
- }
- if (fullImage != null) {
- int rotation = getRotation(mActivity, data);
- final int squareSize = Math.min(fullImage.getWidth(),
- fullImage.getHeight());
- final int left = (fullImage.getWidth() - squareSize) / 2;
- final int top = (fullImage.getHeight() - squareSize) / 2;
-
- Matrix matrix = new Matrix();
- RectF rectSource = new RectF(left, top,
- left + squareSize, top + squareSize);
- RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
- matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
- matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
- canvas.drawBitmap(fullImage, matrix, new Paint());
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- /**
- * Reads the image's exif data and determines the rotation degree needed to display the image
- * in portrait mode.
- */
- private int getRotation(Context context, Uri selectedImage) {
- int rotation = -1;
- try {
- InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
- ExifInterface exif = new ExifInterface(imageStream);
- rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
- } catch (IOException exception) {
- Log.e(TAG, "Error while getting rotation", exception);
- }
-
- switch (rotation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- return 90;
- case ExifInterface.ORIENTATION_ROTATE_180:
- return 180;
- case ExifInterface.ORIENTATION_ROTATE_270:
- return 270;
- default:
- return 0;
- }
+ });
}
private void onPhotoProcessed(Bitmap bitmap) {
@@ -386,29 +168,6 @@
.getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
mImageView.setImageDrawable(mNewUserPhotoDrawable);
}
- new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
- new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
- }
-
- private static int getPhotoSize(Context context) {
- try (Cursor cursor = context.getContentResolver().query(
- DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
- if (cursor != null) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } else {
- return DEFAULT_PHOTO_SIZE;
- }
- }
- }
-
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File fullPath = new File(mImagesDir, fileName);
- if (purge) {
- fullPath.delete();
- }
- return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
}
File saveNewUserPhotoBitmap() {
@@ -435,84 +194,4 @@
void removeNewUserPhotoBitmapFile() {
new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
}
-
- private static final class RestrictedMenuItem {
- private final Context mContext;
- private final String mTitle;
- private final Runnable mAction;
- private final RestrictedLockUtils.EnforcedAdmin mAdmin;
- // Restriction may be set by system or something else via UserManager.setUserRestriction().
- private final boolean mIsRestrictedByBase;
-
- /**
- * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
- *
- * @param context A context.
- * @param title The title of the menu item.
- * @param restriction The restriction, that if is set, blocks the menu item.
- * @param action The action on menu item click.
- */
- RestrictedMenuItem(Context context, String title, String restriction,
- Runnable action) {
- mContext = context;
- mTitle = title;
- mAction = action;
-
- final int myUserId = UserHandle.myUserId();
- mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
- restriction, myUserId);
- mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- restriction, myUserId);
- }
-
- @Override
- public String toString() {
- return mTitle;
- }
-
- void doAction() {
- if (isRestrictedByBase()) {
- return;
- }
-
- if (isRestrictedByAdmin()) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
- return;
- }
-
- mAction.run();
- }
-
- boolean isRestrictedByAdmin() {
- return mAdmin != null;
- }
-
- boolean isRestrictedByBase() {
- return mIsRestrictedByBase;
- }
- }
-
- /**
- * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
- * any element can be restricted by admin (profile owner or device owner).
- */
- private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
- RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
- super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = super.getView(position, convertView, parent);
- final RestrictedMenuItem item = getItem(position);
- final TextView text = (TextView) view.findViewById(R.id.text);
- final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
-
- text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
- image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
- ? ImageView.VISIBLE : ImageView.GONE);
-
- return view;
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
index 165c280..b8615a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -40,12 +40,12 @@
/**
* Check if the current user can perform any activity for
- * android.intent.action.GET_CONTENT action for images.
+ * ACTION_PICK_IMAGES action for images.
* Returns false if the device is currently locked and
* requires a PIN, pattern or password to unlock.
*/
public static boolean canChoosePhoto(Context context) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("image/*");
boolean canPerformActivityForGetImage =
context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
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/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
new file mode 100644
index 0000000..64f8bef
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.graphics.drawable.Drawable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppIconCacheManagerTest {
+
+ private static final String APP_PACKAGE_NAME = "com.test.app";
+ private static final int APP_UID = 9999;
+
+ @Mock
+ private Drawable mIcon;
+
+ private AppIconCacheManager mAppIconCacheManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAppIconCacheManager = AppIconCacheManager.getInstance();
+ doReturn(10).when(mIcon).getIntrinsicHeight();
+ doReturn(10).when(mIcon).getIntrinsicWidth();
+ doReturn(mIcon).when(mIcon).mutate();
+ }
+
+ @After
+ public void tearDown() {
+ AppIconCacheManager.release();
+ }
+
+ @Test
+ public void get_invalidPackageOrUid_shouldReturnNull() {
+ assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull();
+ }
+
+ @Test
+ public void put_invalidPackageOrUid_shouldNotCrash() {
+ mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon);
+ // no crash
+ }
+
+ @Test
+ public void put_invalidIcon_shouldNotCacheIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ public void put_invalidIconSize_shouldNotCacheIcon() {
+ doReturn(-1).when(mIcon).getIntrinsicHeight();
+ doReturn(-1).when(mIcon).getIntrinsicWidth();
+
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ public void put_shouldCacheIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void release_noInstance_shouldNotCrash() {
+ mAppIconCacheManager = null;
+
+ AppIconCacheManager.release();
+ // no crash
+ }
+
+ @Test
+ public void release_existInstance_shouldClearCache() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ AppIconCacheManager.release();
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
new file mode 100644
index 0000000..8e448aa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
+
+import com.android.settingslib.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(RobolectricTestRunner.class)
+public class AppUtilsTest {
+
+ private static final String APP_PACKAGE_NAME = "com.test.app";
+ private static final int APP_UID = 9999;
+
+ @Mock
+ private Drawable mIcon;
+
+ private Context mContext;
+ private AppIconCacheManager mAppIconCacheManager;
+ private ApplicationInfo mAppInfo;
+ private ApplicationsState.AppEntry mAppEntry;
+ private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mAppIconCacheManager = AppIconCacheManager.getInstance();
+ mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID);
+ mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
+ mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
+ doReturn(mIcon).when(mIcon).mutate();
+ }
+
+ @After
+ public void tearDown() {
+ AppIconCacheManager.release();
+ }
+
+ @Test
+ public void getIcon_nullAppEntry_shouldReturnNull() {
+ assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull();
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void getIcon_noCachedIcon_shouldNotReturnNull() {
+ assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull();
+ }
+
+ @Test
+ public void getIcon_existCachedIcon_shouldReturnCachedIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void getIconFromCache_nullAppEntry_shouldReturnNull() {
+ assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull();
+ }
+
+ @Test
+ public void getIconFromCache_shouldReturnCachedIcon() {
+ mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon);
+
+ assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon);
+ }
+
+ @Test
+ public void preloadTopIcons_nullAppEntries_shouldNotCrash() {
+ AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1);
+ // no crash
+ }
+
+ @Test
+ public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() {
+ AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0);
+
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException {
+ AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1);
+
+ TimeUnit.SECONDS.sleep(1);
+ assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull();
+ }
+
+ private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
+ ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
+ appEntry.label = "label";
+ appEntry.mounted = true;
+ final File apkFile = mock(File.class);
+ doReturn(true).when(apkFile).exists();
+ try {
+ Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile");
+ field.setAccessible(true);
+ field.set(appEntry, apkFile);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ fail("Not able to mock apkFile: " + e);
+ }
+ return appEntry;
+ }
+
+ private ApplicationInfo createApplicationInfo(String packageName, int uid) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.sourceDir = "appPath";
+ appInfo.packageName = packageName;
+ appInfo.uid = uid;
+ return appInfo;
+ }
+
+ @Implements(Utils.class)
+ private static class ShadowUtils {
+ @Implementation
+ public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
+ final Drawable icon = mock(Drawable.class);
+ doReturn(10).when(icon).getIntrinsicHeight();
+ doReturn(10).when(icon).getIntrinsicWidth();
+ doReturn(icon).when(icon).mutate();
+ return icon;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 10ccd22..1f2297b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -33,6 +33,7 @@
import static org.robolectric.shadow.api.Shadow.extract;
import android.annotation.UserIdInt;
+import android.app.Application;
import android.app.ApplicationPackageManager;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
@@ -110,6 +111,7 @@
private ApplicationsState mApplicationsState;
private Session mSession;
+ private Application mApplication;
@Mock
private Callbacks mCallbacks;
@@ -190,6 +192,7 @@
ShadowContextImpl shadowContext = Shadow.extract(
RuntimeEnvironment.application.getBaseContext());
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
+ mApplication = spy(RuntimeEnvironment.application);
StorageStats storageStats = new StorageStats();
storageStats.codeBytes = 10;
storageStats.cacheBytes = 30;
@@ -207,8 +210,7 @@
anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
ApplicationsState.sInstance = null;
- mApplicationsState =
- ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
+ mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
mApplicationsState.clearEntries();
mSession = mApplicationsState.newSession(mCallbacks);
@@ -703,6 +705,23 @@
verify(mApplicationsState, never()).clearEntries();
}
+ @Test
+ public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() {
+ when(mApplication.getPackageName()).thenReturn("com.android.settings");
+ mSession.onResume();
+
+ addApp(HOME_PACKAGE_NAME, 1);
+ addApp(LAUNCHABLE_PACKAGE_NAME, 2);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ processAllMessages();
+ verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
+
+ List<AppEntry> appEntries = mAppEntriesCaptor.getValue();
+ for (AppEntry appEntry : appEntries) {
+ assertThat(appEntry.icon).isNull();
+ }
+ }
+
private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps,
ArrayList<ApplicationInfo> profileApps)
throws RemoteException {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index f167721..d7b366e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -60,6 +60,8 @@
private BluetoothDevice mDevice;
@Mock
private BluetoothA2dp mBluetoothA2dp;
+ @Mock
+ private BluetoothAdapter mBluetoothAdapter;
private BluetoothProfile.ServiceListener mServiceListener;
private A2dpProfile mProfile;
@@ -72,7 +74,8 @@
mProfile = new A2dpProfile(mContext, mDeviceManager, mProfileManager);
mServiceListener = mShadowBluetoothAdapter.getServiceListener();
mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
- when(mBluetoothA2dp.getActiveDevice()).thenReturn(mDevice);
+ when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.A2DP)))
+ .thenReturn(Arrays.asList(mDevice));
}
@Test
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/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
index c1cc3ae..445701f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
@@ -33,7 +34,6 @@
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -55,7 +55,6 @@
}
@Test
- @Ignore
public void testBroadcastReceiver() {
final AbstractConnectivityPreferenceController preferenceController =
spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle));
@@ -73,7 +72,7 @@
verify(mContext, times(1))
.registerReceiver(receiverArgumentCaptor.capture(),
filterArgumentCaptor.capture(),
- anyString(), nullable(Handler.class));
+ anyString(), nullable(Handler.class), anyInt());
final BroadcastReceiver receiver = receiverArgumentCaptor.getValue();
final IntentFilter filter = filterArgumentCaptor.getValue();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 53d4653..86f7850 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -24,8 +24,6 @@
import android.content.Context;
import android.content.res.Resources;
-import com.android.settingslib.R;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -53,10 +51,15 @@
final Resources res = mock(Resources.class);
when(mContext.getResources()).thenReturn(res);
- when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn(
+ when(res.getIntArray(
+ com.android.internal.R.array.config_supportedDreamComplications)).thenReturn(
SUPPORTED_DREAM_COMPLICATIONS);
- when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
+ when(res.getIntArray(
+ com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
DEFAULT_DREAM_COMPLICATIONS);
+ when(res.getStringArray(
+ com.android.internal.R.array.config_disabledDreamComponents)).thenReturn(
+ new String[]{});
mBackend = new DreamBackend(mContext);
}
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/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index d6c8816..a5ee4c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -62,7 +62,7 @@
@Mock
private ActivityStarter mActivityStarter;
- private boolean mCanChangePhoto;
+ private boolean mPhotoRestrictedByBase;
private Activity mActivity;
private TestEditUserInfoController mController;
@@ -85,8 +85,8 @@
}
@Override
- boolean canChangePhoto(Context context) {
- return mCanChangePhoto;
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return mPhotoRestrictedByBase;
}
}
@@ -96,7 +96,7 @@
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
mController = new TestEditUserInfoController();
- mCanChangePhoto = true;
+ mPhotoRestrictedByBase = true;
}
@Test
@@ -260,7 +260,7 @@
@Test
public void createDialog_canNotChangePhoto_nullPhotoController() {
- mCanChangePhoto = false;
+ mPhotoRestrictedByBase = false;
mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
"test", "title", null, null);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 10ac829..3b18c57 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -64,6 +64,20 @@
}
@Test
+ public void setLearnMoreText_shouldSetAsTextInLearnMore() {
+ final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+ mFooterPreference.setLearnMoreText("Custom learn more");
+ mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
+
+ mFooterPreference.onBindViewHolder(holder);
+
+ assertThat(((TextView) holder.findViewById(
+ R.id.settingslib_learn_more)).getText().toString())
+ .isEqualTo("Custom learn more");
+ }
+
+ @Test
public void setContentDescription_contentSet_shouldGetSameContentDescription() {
mFooterPreference.setContentDescription("test");
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 00b5f50..a6bfc408b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -84,6 +84,7 @@
Settings.System.RING_VIBRATION_INTENSITY,
Settings.System.HAPTIC_FEEDBACK_INTENSITY,
Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+ Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
Settings.System.DISPLAY_COLOR_MODE,
Settings.System.ALARM_ALERT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 38ff18a..246466e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -313,6 +313,7 @@
VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 6bcb769..06712cc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -124,6 +124,7 @@
VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+ VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index dec3245..b851232 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -46,7 +46,7 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
import com.android.internal.util.XmlUtils;
@@ -783,7 +783,7 @@
+ " VALUES(?,?);");
loadSetting(stmt, Global.SET_INSTALL_LOCATION, 0);
loadSetting(stmt, Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@@ -1176,8 +1176,6 @@
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
- loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
- com.android.internal.R.string.config_dreamsDefaultComponent);
loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
com.android.internal.R.string.config_dreamsDefaultComponent);
@@ -2362,8 +2360,6 @@
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
loadBooleanSetting(stmt, Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
- loadStringSetting(stmt, Settings.Secure.SCREENSAVER_COMPONENTS,
- com.android.internal.R.string.config_dreamsDefaultComponent);
loadStringSetting(stmt, Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
com.android.internal.R.string.config_dreamsDefaultComponent);
@@ -2538,7 +2534,7 @@
loadSetting(stmt, Settings.Global.SET_INSTALL_LOCATION, 0);
loadSetting(stmt, Settings.Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
// Set default cdma emergency tone
loadSetting(stmt, Settings.Global.EMERGENCY_TONE, 0);
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/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 720fb6c..13ae870 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -326,6 +326,8 @@
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
Settings.Global.LOW_POWER_MODE_STICKY,
Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ Settings.Global.LOW_POWER_STANDBY_ENABLED,
Settings.Global.LTE_SERVICE_FORCED,
Settings.Global.LID_BEHAVIOR,
Settings.Global.MAX_ERROR_BYTES_PREFIX,
@@ -656,7 +658,8 @@
Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
- Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER);
+ Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
+ Settings.Global.Wearable.WET_MODE_ON);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c6fbfd8..6f4cd4a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -192,6 +192,9 @@
<uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" />
+ <!-- Permission required for processes that don't own the focused window to switch
+ touch mode state -->
+ <uses-permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" />
<!-- Permission required to test onPermissionsChangedListener -->
<uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
<uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" />
@@ -204,7 +207,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" />
@@ -249,6 +251,7 @@
<uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
<uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
<uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
+ <uses-permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" />
<uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
<uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
<uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
@@ -309,6 +312,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 +340,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 +533,8 @@
<!-- 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.NEARBY_WIFI_DEVICES" />
+ <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 +566,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 +637,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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f35f5dd..776a511 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -213,6 +213,9 @@
<!-- DevicePolicyManager get user restrictions -->
<uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
+ <!-- DevicePolicyManager get admin policy -->
+ <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" />
+
<!-- TV picture-in-picture -->
<uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" />
@@ -301,6 +304,11 @@
<!-- For clipboard overlay -->
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
+
+ <!-- To change system language (HDMI CEC) -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION" />
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
@@ -485,6 +493,16 @@
android:excludeFromRecents="true">
</activity>
+ <!-- started from HdmiCecLocalDevicePlayback -->
+ <activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity"
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:permission="android.permission.CHANGE_CONFIGURATION"
+ android:theme="@style/BottomSheet"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true">
+ </activity>
+
<!-- started from SensoryPrivacyService -->
<activity android:name=".sensorprivacy.SensorUseStartedActivity"
android:exported="true"
@@ -855,11 +873,11 @@
android:singleUser="true"
android:permission="android.permission.BIND_DREAM_SERVICE" />
- <!-- Service for external clients to do media transfer -->
- <!-- TODO(b/203800643): Export and guard with a permission. -->
+ <!-- Service for external clients to notify us of nearby media devices -->
+ <!-- TODO(b/216313420): Export and guard with a permission. -->
<service
- android:name=".media.taptotransfer.sender.MediaTttSenderService"
- />
+ android:name=".media.nearby.NearbyMediaDevicesService"
+ />
<receiver
android:name=".tuner.TunerService$ClearReceiver"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index dee4ff5..9722b1f 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,7 +1,7 @@
{
// Looking for unit test presubmit configuration?
// This currently lives in ATP config apct/system_ui/unit_test
- "presubmit": [
+ "presubmit-large": [
{
"name": "PlatformScenarioTests",
"options": [
@@ -24,7 +24,9 @@
"exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
}
]
- },
+ }
+ ],
+ "presubmit": [
{
"name": "SystemUIGoogleTests",
"options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 08d217d..4540b77 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -301,6 +301,7 @@
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
+ @JvmDefault
fun onIntentStarted(willAnimate: Boolean) {}
/**
@@ -308,6 +309,7 @@
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
* before the cancellation.
*/
+ @JvmDefault
fun onLaunchAnimationCancelled() {}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index f7a7603..3051d80 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -19,6 +19,7 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.app.ActivityManager
import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
@@ -45,7 +46,8 @@
class DialogLaunchAnimator @JvmOverloads constructor(
private val dreamManager: IDreamManager,
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
- private var isForTesting: Boolean = false
+ // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed
+ private var forceDisableSynchronization: Boolean = ActivityManager.isLowRamDeviceStatic()
) {
private companion object {
private val TIMINGS = ActivityLaunchAnimator.TIMINGS
@@ -111,7 +113,7 @@
dialog = dialog,
animateBackgroundBoundsChange,
animatedParent,
- isForTesting
+ forceDisableSynchronization
)
openedDialogs.add(animatedDialog)
@@ -187,10 +189,9 @@
private val parentAnimatedDialog: AnimatedDialog? = null,
/**
- * Whether we are currently running in a test, in which case we need to disable
- * synchronization.
+ * Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
- private val isForTesting: Boolean
+ private val forceDisableSynchronization: Boolean
) {
/**
* The DecorView of this dialog window.
@@ -420,8 +421,9 @@
* (or inversely, removed from the UI when the touch surface is made visible).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
- if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
- !decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
+ if (forceDisableSynchronization ||
+ !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
+ !decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
// No need to synchronize if either the touch surface or dialog view is not attached
// to a window.
then()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index ebe96eb..77386cf 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -100,9 +100,11 @@
* needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
* fully above the [launchContainer].
*/
+ @JvmDefault
fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
/** The animation made progress and the expandable view [state] should be updated. */
+ @JvmDefault
fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
/**
@@ -110,6 +112,7 @@
* called previously. This is typically used to clean up the resources initialized when the
* animation was started.
*/
+ @JvmDefault
fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 29221aa..208825c 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -103,6 +103,20 @@
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0))
)),
+ RAINBOW(CoreSpec(
+ a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
+ a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
+ a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)),
+ n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0)),
+ n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 0.0))
+ )),
+ FRUIT_SALAD(CoreSpec(
+ a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.GTE, 48.0)),
+ a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 50.0), Chroma(ChromaStrategy.EQ, 36.0)),
+ a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 36.0)),
+ n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 10.0)),
+ n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
+ )),
}
class ColorScheme(
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 68c8c3e..ffee894 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,6 +20,7 @@
import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.uitemplatedata.SmartspaceTapAction;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -143,6 +144,18 @@
}
}
+ default void startFromAction(SmartspaceTapAction action, View v, boolean showOnLockscreen) {
+ try {
+ if (action.getIntent() != null) {
+ startIntent(v, action.getIntent(), showOnLockscreen);
+ } else if (action.getPendingIntent() != null) {
+ startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Could not launch intent for action: " + action, e);
+ }
+ }
+
/** Start the intent */
void startIntent(View v, Intent i, boolean showOnLockscreen);
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index ecb3cb3..339cab4 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -19,8 +19,10 @@
<com.android.systemui.qs.FooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="48dp"
- android:gravity="center_vertical">
+ android:layout_height="@dimen/qs_footer_height"
+ android:gravity="center_vertical"
+ android:layout_gravity="bottom"
+>
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
new file mode 100644
index 0000000..95bdd89
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
@@ -0,0 +1,102 @@
+<?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.
+-->
+
+<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
+<com.android.systemui.qs.FooterActionsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_footer_height"
+ android:gravity="center_vertical"
+ android:layout_gravity="bottom"
+>
+
+ <View
+ android:layout_height="1dp"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ />
+
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ >
+
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:background="@drawable/qs_footer_action_circle"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:background="@drawable/qs_footer_action_circle"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_gravity="center"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/tuner_icon"
+ android:layout_width="8dp"
+ android:layout_height="8dp"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="@dimen/qs_footer_icon_padding"
+ android:src="@drawable/tuner"
+ android:tint="?android:attr/textColorTertiary"
+ android:visibility="invisible" />
+
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle_color"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?androidprv:attr/textColorOnAccent" />
+
+ </LinearLayout>
+</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
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/drawable/action_chip_background.xml b/packages/SystemUI/res/drawable/action_chip_background.xml
index eeff39b..745470f 100644
--- a/packages/SystemUI/res/drawable/action_chip_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_background.xml
@@ -17,11 +17,11 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="@color/screenshot_button_ripple">
+ android:color="@color/overlay_button_ripple">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorAccentSecondary"/>
- <corners android:radius="@dimen/screenshot_button_corner_radius"/>
+ <corners android:radius="@dimen/overlay_button_corner_radius"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/drawable/action_chip_container_background.xml b/packages/SystemUI/res/drawable/action_chip_container_background.xml
index 72767a1..36083f1 100644
--- a/packages/SystemUI/res/drawable/action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/action_chip_container_background.xml
@@ -19,5 +19,5 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface"/>
- <corners android:radius="@dimen/screenshot_action_container_corner_radius"/>
+ <corners android:radius="@dimen/overlay_action_container_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
index c547c52..ec9465b 100644
--- a/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
+++ b/packages/SystemUI/res/drawable/auth_dialog_enterprise.xml
@@ -20,6 +20,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z"
+ android:pathData="@*android:string/config_work_badge_path_24"
android:fillColor="?android:attr/colorAccent"/>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SystemUI/res/drawable/ic_media_next.xml
similarity index 69%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SystemUI/res/drawable/ic_media_next.xml
index ff57406..016653b 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SystemUI/res/drawable/ic_media_next.xml
@@ -12,14 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
-</vector>
\ No newline at end of file
+ android:pathData="M0,12L8.5,6L0,0V12ZM2,3.86L5.03,6L2,8.14V3.86ZM12,0H10V12H12V0Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SystemUI/res/drawable/ic_media_pause.xml
similarity index 67%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SystemUI/res/drawable/ic_media_pause.xml
index ff57406..1f4b2cf 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SystemUI/res/drawable/ic_media_pause.xml
@@ -12,14 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="14dp"
+ android:height="16dp"
+ android:viewportWidth="14"
+ android:viewportHeight="16">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
+ android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z"
+ android:fillColor="#FFFFFF"
+ android:fillType="evenOdd"/>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SystemUI/res/drawable/ic_media_play.xml
similarity index 69%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SystemUI/res/drawable/ic_media_play.xml
index ff57406..0eac1ad 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SystemUI/res/drawable/ic_media_play.xml
@@ -12,14 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
+ android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z"
+ android:fillColor="#FFFFFF"
+ android:fillType="evenOdd"/>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/packages/SystemUI/res/drawable/ic_media_prev.xml
similarity index 69%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
copy to packages/SystemUI/res/drawable/ic_media_prev.xml
index ff57406..b4aeed4 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
+++ b/packages/SystemUI/res/drawable/ic_media_prev.xml
@@ -12,14 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
<path
- android:fillColor="@android:color/system_neutral2_400"
- android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
-</vector>
\ No newline at end of file
+ android:pathData="M0,0H2V12H0V0ZM3.5,6L12,12V0L3.5,6ZM6.97,6L10,8.14V3.86L6.97,6Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
similarity index 91%
rename from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
rename to packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
index dd818a0..d8f5632 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/overlay_actions_background_protection.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
+ android:startColor="@color/overlay_background_protection_start"
android:endColor="#00000000"/>
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_button_background.xml b/packages/SystemUI/res/drawable/overlay_button_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_button_background.xml
rename to packages/SystemUI/res/drawable/overlay_button_background.xml
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
new file mode 100644
index 0000000..f54c30f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetTop="@dimen/qs_footer_action_inset"
+ android:insetBottom="@dimen/qs_footer_action_inset"
+ android:insetLeft="@dimen/qs_footer_action_inset"
+ android:insetRight="@dimen/qs_footer_action_inset">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval">
+ <solid android:color="@android:color/white"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="?attr/offStateColor"/>
+ </shape>
+ </item>
+
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
new file mode 100644
index 0000000..1a323bc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetTop="@dimen/qs_footer_action_inset"
+ android:insetBottom="@dimen/qs_footer_action_inset"
+ android:insetLeft="@dimen/qs_footer_action_inset"
+ android:insetRight="@dimen/qs_footer_action_inset">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval">
+ <solid android:color="@android:color/white"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="?android:attr/colorAccent"/>
+ </shape>
+ </item>
+
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/qs_media_scrim.xml
similarity index 62%
copy from packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
copy to packages/SystemUI/res/drawable/qs_media_scrim.xml
index dd818a0..2ec319c 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/qs_media_scrim.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -12,11 +12,15 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
+ ~ limitations under the License
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/notification_corner_radius"/>
+ <!-- gradient from 25% in the center to 100% at edges -->
<gradient
- android:angle="90"
- android:startColor="@color/screenshot_background_protection_start"
- android:endColor="#00000000"/>
-</shape>
\ No newline at end of file
+ android:type="radial"
+ android:gradientRadius="100%p"
+ android:startColor="#40000000"
+ android:endColor="#FF000000" />
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 1e0ce00..0ff1db2 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,64 +18,72 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:orientation="vertical">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="5"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4939ea2..dada981 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,76 +22,81 @@
android:gravity="center_horizontal"
android:elevation="@dimen/biometric_dialog_elevation">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
-
- <TextView
- android:id="@+id/error"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:layout_below="@id/auth_credential_header"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingBottom="16dp"
+ android:paddingTop="60dp">
- </LinearLayout>
+ <FrameLayout
+ style="@style/LockPatternContainerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ style="@style/LockPatternStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 7ffb3b2..c58c001 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,123 +14,137 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
+<FrameLayout
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
- android:id="@+id/actions_container_background"
- android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
- android:elevation="1dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
- app:layout_constraintBottom_toBottomOf="@+id/actions_container"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:paddingEnd="@dimen/screenshot_action_container_padding_right"
- android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
- android:elevation="1dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@+id/preview_border"
- app:layout_constraintEnd_toEndOf="parent">
- <LinearLayout
- android:id="@+id/actions"
+ android:id="@+id/background_protection"
+ android:layout_height="@dimen/overlay_bg_protection_height"
+ android:layout_width="match_parent"
+ android:layout_gravity="bottom"
+ android:src="@drawable/overlay_actions_background_protection"/>
+ <com.android.systemui.clipboardoverlay.DraggableConstraintLayout
+ android:id="@+id/clipboard_ui"
+ android:theme="@style/FloatingOverlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:elevation="1dp"
+ android:background="@drawable/action_chip_container_background"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/actions_container"
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="1dp"
+ android:scrollbars="none"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintWidth_percent="1.0"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/preview_border"
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true">
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/remote_copy_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/edit_chip"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <View
+ android:id="@+id/preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="@dimen/overlay_offset_y"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/actions_container_background"
+ android:elevation="@dimen/overlay_preview_elevation"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+ android:background="@drawable/overlay_border"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_end"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <include layout="@layout/screenshot_action_chip"
- android:id="@+id/remote_copy_chip"/>
- <include layout="@layout/screenshot_action_chip"
- android:id="@+id/edit_chip"/>
- </LinearLayout>
- </HorizontalScrollView>
- <View
- android:id="@+id/preview_border"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_marginStart="@dimen/overlay_offset_x"
- android:layout_marginBottom="@dimen/overlay_offset_y"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:elevation="@dimen/overlay_preview_elevation"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
- android:background="@drawable/overlay_border"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierMargin="@dimen/overlay_border_width"
- app:barrierDirection="end"
- app:constraint_referenced_ids="clipboard_preview"/>
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/clipboard_preview_top"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierDirection="top"
- app:barrierMargin="@dimen/overlay_border_width_neg"
- app:constraint_referenced_ids="clipboard_preview"/>
- <FrameLayout
- android:id="@+id/clipboard_preview"
- android:elevation="@dimen/overlay_preview_elevation"
- android:background="@drawable/overlay_preview_background"
- android:clipChildren="true"
- android:clipToOutline="true"
- android:clipToPadding="true"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_margin="@dimen/overlay_border_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- app:layout_constraintBottom_toBottomOf="@id/preview_border"
- app:layout_constraintStart_toStartOf="@id/preview_border"
- app:layout_constraintEnd_toEndOf="@id/preview_border"
- app:layout_constraintTop_toTopOf="@id/preview_border">
- <TextView android:id="@+id/text_preview"
- android:textFontWeight="500"
- android:padding="8dp"
- android:gravity="center|start"
- android:ellipsize="end"
- android:autoSizeTextType="uniform"
- android:autoSizeMinTextSize="10sp"
- android:autoSizeMaxTextSize="200sp"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_width="@dimen/clipboard_preview_size"
- android:layout_height="@dimen/clipboard_preview_size"/>
- <ImageView
- android:id="@+id/image_preview"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </FrameLayout>
- <FrameLayout
- android:id="@+id/dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- android:elevation="@dimen/overlay_dismiss_button_elevation"
- android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
- android:contentDescription="@string/clipboard_dismiss_description">
- <ImageView
- android:id="@+id/dismiss_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:src="@drawable/overlay_cancel"/>
- </FrameLayout>
-</com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
+ android:layout_height="wrap_content"
+ app:barrierMargin="@dimen/overlay_border_width"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="top"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <FrameLayout
+ android:id="@+id/clipboard_preview"
+ android:elevation="@dimen/overlay_preview_elevation"
+ android:background="@drawable/overlay_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_margin="@dimen/overlay_border_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:layout_constraintBottom_toBottomOf="@id/preview_border"
+ app:layout_constraintStart_toStartOf="@id/preview_border"
+ app:layout_constraintEnd_toEndOf="@id/preview_border"
+ app:layout_constraintTop_toTopOf="@id/preview_border">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="10sp"
+ android:autoSizeMaxTextSize="200sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <ImageView
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="@dimen/overlay_dismiss_button_elevation"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ android:contentDescription="@string/clipboard_dismiss_description">
+ <ImageView
+ android:id="@+id/dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+ </com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 7fd029c..11a5665 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -35,7 +35,8 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
- android:clipToPadding="false" />
+ android:clipToPadding="false"
+ android:paddingHorizontal="@dimen/controls_padding_horizontal" />
</com.android.systemui.globalactions.MinHeightScrollView>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
new file mode 100644
index 0000000..b6f516f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<TextClock
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/date_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_clock_date_padding_left"
+ android:paddingBottom="@dimen/dream_overlay_complication_clock_date_padding_bottom"
+ android:gravity="center_horizontal"
+ android:textColor="@android:color/white"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:format12Hour="EEE, MMM d"
+ android:format24Hour="EEE, MMM d"
+ android:textSize="@dimen/dream_overlay_complication_clock_date_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
new file mode 100644
index 0000000..82c8d5f
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<TextClock
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/time_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_clock_time_padding_left"
+ android:fontFamily="@font/clock"
+ android:textColor="@android:color/white"
+ android:format12Hour="h:mm"
+ android:format24Hour="kk:mm"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
new file mode 100644
index 0000000..08f0d67
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/weather_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/dream_overlay_complication_weather_padding_left"
+ android:paddingBottom="@dimen/dream_overlay_complication_weather_padding_bottom"
+ android:textColor="@android:color/white"
+ android:shadowColor="@color/keyguard_shadow_color"
+ android:shadowRadius="?attr/shadowRadius"
+ android:textSize="@dimen/dream_overlay_complication_weather_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index 5135947..f4eb32f 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -20,30 +20,28 @@
android:id="@+id/dream_overlay_complications_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <TextClock
- android:id="@+id/time_view"
+ <androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="sans-serif-thin"
- android:format12Hour="h:mm"
- android:format24Hour="kk:mm"
- android:shadowColor="#B2000000"
- android:shadowRadius="2.0"
- android:singleLine="true"
- android:textSize="72sp"
- app:layout_constraintBottom_toTopOf="@+id/date_view"
- app:layout_constraintStart_toStartOf="parent" />
- <TextClock
- android:id="@+id/date_view"
+ android:id="@+id/complication_top_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_top_percent"
+ android:orientation="horizontal"/>
+ <androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:shadowColor="#B2000000"
- android:shadowRadius="2.0"
- android:format12Hour="EEE, MMM d"
- android:format24Hour="EEE, MMM d"
- android:singleLine="true"
- android:textSize="18sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="@+id/time_view"
- app:layout_constraintStart_toStartOf="@+id/time_view" />
+ android:id="@+id/complication_end_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_end_percent"
+ android:orientation="vertical"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/complication_bottom_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_bottom_percent"
+ android:orientation="horizontal"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/complication_start_guide"
+ app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_start_percent"
+ android:orientation="vertical"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 4929f50..3c2183d 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -25,7 +25,7 @@
android:id="@+id/dream_overlay_content"
android:layout_width="match_parent"
android:layout_height="0dp"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar"
app:layout_constraintBottom_toBottomOf="parent" />
<com.android.systemui.dreams.DreamOverlayStatusBarView
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 856697c..cdf6103 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -18,7 +18,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:background="?android:colorBackgroundFloating"
android:id="@+id/root"
android:layout_width="match_parent"
@@ -32,7 +31,7 @@
android:text="@string/save"
android:layout_marginStart="8dp"
android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
- android:background="@drawable/screenshot_button_background"
+ android:background="@drawable/overlay_button_background"
android:textColor="?android:textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -46,7 +45,7 @@
android:text="@android:string/cancel"
android:layout_marginStart="6dp"
android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
- android:background="@drawable/screenshot_button_background"
+ android:background="@drawable/overlay_button_background"
android:textColor="?android:textColorSecondary"
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index cc02fea..51d1608 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -28,6 +28,22 @@
android:background="@drawable/qs_media_background"
android:theme="@style/MediaPlayer">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="184dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:translationZ="0dp"
+ android:id="@+id/album_art"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ android:clipToOutline="true"
+ android:foreground="@drawable/qs_media_scrim"
+ android:background="@drawable/qs_media_scrim"
+ />
+
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
android:layout_width="wrap_content"
@@ -281,6 +297,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
@@ -304,6 +321,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index b546a9c..9471b9f 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -264,6 +264,7 @@
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
@@ -288,6 +289,7 @@
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
+ android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
diff --git a/packages/SystemUI/res/layout/screenshot_action_chip.xml b/packages/SystemUI/res/layout/overlay_action_chip.xml
similarity index 61%
rename from packages/SystemUI/res/layout/screenshot_action_chip.xml
rename to packages/SystemUI/res/layout/overlay_action_chip.xml
index b80469f..e0c20ff 100644
--- a/packages/SystemUI/res/layout/screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/overlay_action_chip.xml
@@ -14,33 +14,34 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.screenshot.ScreenshotActionChip
+<com.android.systemui.screenshot.OverlayActionChip
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/screenshot_action_chip"
+ android:id="@+id/overlay_action_chip"
+ android:theme="@style/FloatingOverlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/screenshot_action_chip_margin_start"
- android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical"
+ android:layout_marginStart="@dimen/overlay_action_chip_margin_start"
+ android:paddingVertical="@dimen/overlay_action_chip_margin_vertical"
android:layout_gravity="center"
android:gravity="center"
android:alpha="0.0">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
android:background="@drawable/action_chip_background"
android:gravity="center">
<ImageView
- android:id="@+id/screenshot_action_chip_icon"
- android:tint="?android:attr/textColorPrimary"
- android:layout_width="@dimen/screenshot_action_chip_icon_size"
- android:layout_height="@dimen/screenshot_action_chip_icon_size"/>
+ android:id="@+id/overlay_action_chip_icon"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="@dimen/overlay_action_chip_icon_size"
+ android:layout_height="@dimen/overlay_action_chip_icon_size"/>
<TextView
- android:id="@+id/screenshot_action_chip_text"
+ android:id="@+id/overlay_action_chip_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="@dimen/screenshot_action_chip_text_size"
- android:textColor="?android:attr/textColorPrimary"/>
+ android:textSize="@dimen/overlay_action_chip_text_size"
+ android:textColor="?attr/overlayButtonTextColor"/>
</LinearLayout>
-</com.android.systemui.screenshot.ScreenshotActionChip>
+</com.android.systemui.screenshot.OverlayActionChip>
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/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 5cd9e94..b6e3499 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer"
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
+ android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_footer_margin"
android:layout_marginEnd="@dimen/qs_footer_margin"
android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -36,7 +36,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/qs_footer_height"
android:layout_gravity="center_vertical">
<TextView
@@ -80,8 +80,13 @@
</LinearLayout>
- <include layout="@layout/footer_actions"
- android:id="@+id/qs_footer_actions"/>
+ <ViewStub
+ android:id="@+id/footer_stub"
+ android:inflatedId="@+id/qs_footer_actions"
+ android:layout="@layout/footer_actions"
+ android:layout_height="@dimen/qs_footer_height"
+ android:layout_width="match_parent"
+ />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index f5c6036..22abd0c 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -51,6 +51,15 @@
android:id="@+id/qs_detail"
layout="@layout/qs_detail" />
+ <ViewStub
+ android:id="@+id/container_stub"
+ android:inflatedId="@+id/qs_footer_actions"
+ android:layout="@layout/new_footer_actions"
+ android:layout_height="@dimen/qs_footer_height"
+ android:layout_width="match_parent"
+ android:layout_gravity="bottom"
+ />
+
<include
android:id="@+id/qs_customize"
layout="@layout/qs_customize_panel"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 10a2f4c..2c29f07 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -57,16 +57,6 @@
android:focusable="true"
android:paddingBottom="24dp"
android:importantForAccessibility="yes">
-
- <include
- layout="@layout/footer_actions"
- android:id="@+id/qqs_footer_actions"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/qqs_layout_margin_top"
- android:layout_marginStart="@dimen/qs_footer_margin"
- android:layout_marginEnd="@dimen/qs_footer_margin"
- />
</com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
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 819a9a4..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/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 227212b..890dbe5 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -17,7 +17,7 @@
<com.android.systemui.screenshot.ScreenshotView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/screenshot_frame"
- android:theme="@style/Screenshot"
+ android:theme="@style/FloatingOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no">
@@ -30,11 +30,11 @@
android:importantForAccessibility="no"/>
<ImageView
android:id="@+id/screenshot_actions_background"
- android:layout_height="@dimen/screenshot_bg_protection_height"
+ android:layout_height="@dimen/overlay_bg_protection_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:alpha="0.0"
- android:src="@drawable/screenshot_actions_background_protection"/>
+ android:src="@drawable/overlay_actions_background_protection"/>
<ImageView
android:id="@+id/screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8f791c3..813bb60 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -26,7 +26,7 @@
android:layout_width="0dp"
android:elevation="1dp"
android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container"
@@ -35,9 +35,9 @@
android:id="@+id/screenshot_actions_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:paddingEnd="@dimen/screenshot_action_container_padding_right"
- android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="1dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
@@ -50,11 +50,11 @@
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_share_chip"/>
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_edit_chip"/>
- <include layout="@layout/screenshot_action_chip"
+ <include layout="@layout/overlay_action_chip"
android:id="@+id/screenshot_scroll_chip"
android:visibility="gone" />
</LinearLayout>
@@ -89,7 +89,7 @@
<ImageView
android:id="@+id/screenshot_preview"
android:visibility="invisible"
- android:layout_width="@dimen/screenshot_x_scale"
+ android:layout_width="@dimen/overlay_x_scale"
android:layout_margin="@dimen/overlay_border_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 2290964..39d7f4f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -122,7 +122,7 @@
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
- android:layout_marginBottom="@dimen/close_handle_underlap"
+ android:layout_marginBottom="@dimen/notification_panel_margin_bottom"
android:importantForAccessibility="no"
systemui:layout_constraintStart_toStartOf="parent"
systemui:layout_constraintEnd_toEndOf="parent"
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..88db881 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-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ac4dfd2..8c5006d 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -31,9 +31,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">1</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- Max number of columns for power menu -->
<integer name="power_menu_max_columns">4</integer>
</resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fc5edf3..9d24e9b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,6 @@
<dimen name="controls_management_favorites_top_margin">8dp</dimen>
<dimen name="wallet_card_carousel_container_top_margin">24dp</dimen>
+
+ <dimen name="large_dialog_width">348dp</dimen>
</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-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3412722..b318bbc 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -58,9 +58,9 @@
<!-- The color of the text in the Global Actions menu -->
<color name="global_actions_alert_text">@color/GM2_red_300</color>
- <!-- Global screenshot actions -->
- <color name="screenshot_button_ripple">#42FFFFFF</color>
- <color name="screenshot_background_protection_start">#80000000</color> <!-- 50% black -->
+ <!-- Floating overlay actions -->
+ <color name="overlay_button_ripple">#42FFFFFF</color>
+ <color name="overlay_background_protection_start">#80000000</color> <!-- 50% black -->
<!-- Media -->
<color name="media_divider">#85ffffff</color>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 1f815b7..f7261e7 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -47,8 +47,8 @@
<item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
</style>
- <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight">
- <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
+ <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="overlayButtonTextColor">?android:attr/textColorPrimaryInverse</item>
</style>
<style name="Theme.PeopleTileConfigActivity" parent="@style/Theme.SystemUI">
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-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index dabc310..fe546f6 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 89d046b..c2cec52 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -26,6 +26,10 @@
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
+
+ <dimen name="notification_panel_margin_bottom">48dp</dimen>
+
<!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) -->
<item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
<dimen name="controls_task_view_right_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 02fd25b..3c6a81e 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -15,7 +15,6 @@
~ limitations under the License
-->
<resources>
-
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">6</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f5dc7e3e..1b8453a 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -29,9 +29,6 @@
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">0</integer>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">4</integer>
-
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7d03301..a66ed15 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -69,5 +69,5 @@
<dimen name="qs_detail_margin_top">0dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
- <dimen name="large_dialog_width">504dp</dimen>
+ <dimen name="large_dialog_width">472dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index ae89ef4..be34a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -15,9 +15,6 @@
~ limitations under the License
-->
<resources>
- <!-- Max number of columns for quick controls area -->
- <integer name="controls_max_columns">2</integer>
-
<!-- The maximum number of rows in the QSPanel -->
<integer name="quick_settings_max_rows">3</integer>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
similarity index 62%
copy from packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
copy to packages/SystemUI/res/values-sw720dp-land/dimens.xml
index cb602d79..ae557c4 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -1,5 +1,7 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+<?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.
@@ -12,8 +14,10 @@
* WITHOUT WARRANTIES 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;
-
-parcelable NetworkStateSnapshot;
+*/
+-->
+<resources>
+ <dimen name="controls_padding_horizontal">205dp</dimen>
+ <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
+ <dimen name="notification_panel_margin_bottom">56dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 1564ee8..95df594 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,8 +16,9 @@
*/
-->
<resources>
-
<!-- gap on either side of status bar notification icons -->
<dimen name="status_bar_icon_padding">1dp</dimen>
+
+ <dimen name="controls_padding_horizontal">75dp</dimen>
</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/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/packages/SystemUI/res/values-w500dp/config.xml
similarity index 71%
rename from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
rename to packages/SystemUI/res/values-w500dp/config.xml
index 16dea48..ef499ff 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
+++ b/packages/SystemUI/res/values-w500dp/config.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -14,7 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@android:color/system_neutral2_200">
- <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
-</ripple>
\ No newline at end of file
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">3</integer>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/packages/SystemUI/res/values-w850dp/config.xml
similarity index 71%
copy from libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
copy to packages/SystemUI/res/values-w850dp/config.xml
index 16dea48..337ebe1 100644
--- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
+++ b/packages/SystemUI/res/values-w850dp/config.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
@@ -14,7 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@android:color/system_neutral2_200">
- <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
-</ripple>
\ No newline at end of file
+
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">4</integer>
+</resources>
\ No newline at end of file
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/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index db69924..e6ab0ff 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -85,7 +85,7 @@
Contract: Pixel with fillColor blended over backgroundColor blended over translucent should
equal to singleToneColor blended over translucent. -->
<declare-styleable name="TonedIcon">
- <attr name="backgroundColor" format="integer" />
+ <attr name="iconBackgroundColor" format="integer" />
<attr name="fillColor" format="integer" />
<attr name="singleToneColor" format="integer" />
<attr name="homeHandleColor" format="integer" />
@@ -204,5 +204,7 @@
<attr name="singleLineVerticalPadding" format="dimension" />
<attr name="textViewId" format="reference" />
</declare-styleable>
+
+ <attr name="overlayButtonTextColor" format="color" />
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 81e3e04..3ab569a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -138,9 +138,9 @@
<color name="udfps_enroll_progress">#7DA7F1</color>
<color name="udfps_enroll_progress_help">#ffEE675C</color>
- <!-- Global screenshot actions -->
- <color name="screenshot_button_ripple">#1f000000</color>
- <color name="screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
+ <!-- Floating overlay actions -->
+ <color name="overlay_button_ripple">#1f000000</color>
+ <color name="overlay_background_protection_start">#40000000</color> <!-- 25% black -->
<!-- GM2 colors -->
<color name="GM2_grey_100">#F1F3F4</color>
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..dba7290 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -258,36 +258,36 @@
<!-- Dimensions related to screenshots -->
- <!-- The padding on the global screenshot background image -->
- <dimen name="screenshot_x_scale">80dp</dimen>
- <dimen name="screenshot_bg_protection_height">242dp</dimen>
- <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
- <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
- <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_right">8dp</dimen>
- <!-- Radius of the chip background on global screenshot actions -->
- <dimen name="screenshot_button_corner_radius">8dp</dimen>
- <!-- Margin between successive chips -->
- <dimen name="screenshot_action_chip_margin_start">8dp</dimen>
- <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
- <dimen name="screenshot_action_chip_margin_vertical">4dp</dimen>
- <dimen name="screenshot_action_chip_padding_vertical">11dp</dimen>
- <dimen name="screenshot_action_chip_icon_size">18sp</dimen>
- <!-- Padding on each side of the icon for icon-only chips -->
- <dimen name="screenshot_action_chip_icon_only_padding_horizontal">14dp</dimen>
- <!-- Padding at the edges of the chip for icon-and-text chips -->
- <dimen name="screenshot_action_chip_padding_horizontal">12dp</dimen>
- <!-- Spacing between chip icon and chip text -->
- <dimen name="screenshot_action_chip_spacing">8dp</dimen>
- <dimen name="screenshot_action_chip_text_size">14sp</dimen>
- <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
+
<dimen name="screenshot_crop_handle_thickness">3dp</dimen>
<dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
<!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+ <!-- Constrained size of the floating overlay preview -->
+ <dimen name="overlay_x_scale">80dp</dimen>
+ <!-- Radius of the chip background on floating overlay actions -->
+ <dimen name="overlay_button_corner_radius">8dp</dimen>
+ <!-- Margin between successive chips -->
+ <dimen name="overlay_action_chip_margin_start">8dp</dimen>
+ <!-- Padding to make tappable chip height 48dp (18+11+11+4+4) -->
+ <dimen name="overlay_action_chip_margin_vertical">4dp</dimen>
+ <dimen name="overlay_action_chip_padding_vertical">11dp</dimen>
+ <dimen name="overlay_action_chip_icon_size">18sp</dimen>
+ <!-- Padding on each side of the icon for icon-only chips -->
+ <dimen name="overlay_action_chip_icon_only_padding_horizontal">14dp</dimen>
+ <!-- Padding at the edges of the chip for icon-and-text chips -->
+ <dimen name="overlay_action_chip_padding_horizontal">12dp</dimen>
+ <!-- Spacing between chip icon and chip text -->
+ <dimen name="overlay_action_chip_spacing">8dp</dimen>
+ <dimen name="overlay_action_chip_text_size">14sp</dimen>
<dimen name="overlay_offset_y">8dp</dimen>
<dimen name="overlay_offset_x">16dp</dimen>
<dimen name="overlay_preview_elevation">4dp</dimen>
+ <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
+ <dimen name="overlay_bg_protection_height">242dp</dimen>
+ <dimen name="overlay_action_container_corner_radius">18dp</dimen>
+ <dimen name="overlay_action_container_padding_vertical">4dp</dimen>
+ <dimen name="overlay_action_container_padding_right">8dp</dimen>
<dimen name="overlay_dismiss_button_elevation">7dp</dimen>
<dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
<dimen name="overlay_dismiss_button_margin">8dp</dimen>
@@ -295,7 +295,7 @@
<!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
<dimen name="overlay_border_width_neg">-4dp</dimen>
- <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+ <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen>
<!-- The width of the view containing navigation buttons -->
@@ -311,9 +311,6 @@
<!-- Move the back button drawable for 3 button layout upwards in ime mode and in portrait -->
<dimen name="navbar_back_button_ime_offset">2dp</dimen>
- <!-- Amount of close_handle that will NOT overlap the notification list -->
- <dimen name="close_handle_underlap">32dp</dimen>
-
<!-- Height of the status bar header bar in the car setting. -->
<dimen name="car_status_bar_header_height">128dp</dimen>
@@ -330,7 +327,7 @@
<!-- The height of the quick settings footer that holds the user switcher, settings icon,
etc. -->
- <dimen name="qs_footer_height">96dp</dimen>
+ <dimen name="qs_footer_height">48dp</dimen>
<!-- The size of each of the icon buttons in the QS footer -->
<dimen name="qs_footer_action_button_size">48dp</dimen>
@@ -369,8 +366,19 @@
<!-- The top margin of the panel that holds the list of notifications. -->
<dimen name="notification_panel_margin_top">0dp</dimen>
- <!-- The bottom margin of the panel that holds the list of notifications. -->
- <dimen name="notification_panel_margin_bottom">0dp</dimen>
+ <!-- The minimum content height for the split shade NSSL.
+ It is used because if the height is too small, the expansion motion is too fast.
+ Note that the value of 256dp is more or less a random value and can be changed to tweak
+ the expansion motion.
+ -->
+ <dimen name="nssl_split_shade_min_content_height">256dp</dimen>
+
+ <dimen name="notification_panel_margin_bottom">32dp</dimen>
+
+ <!-- The bottom padding of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_padding_bottom">0dp</dimen>
+
+ <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen>
<dimen name="notification_panel_width">@dimen/match_parent</dimen>
@@ -432,7 +440,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>
@@ -481,7 +491,7 @@
<dimen name="qs_tile_text_size">14sp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
- <dimen name="qs_panel_padding_bottom">0dp</dimen>
+ <dimen name="qs_panel_padding_bottom">@dimen/qs_footer_height</dimen>
<dimen name="qs_panel_padding_top">48dp</dimen>
<dimen name="qs_detail_header_padding">0dp</dimen>
<dimen name="qs_detail_image_width">56dp</dimen>
@@ -1026,6 +1036,7 @@
<dimen name="controls_header_bottom_margin">24dp</dimen>
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
+ <dimen name="controls_padding_horizontal">0dp</dimen>
<dimen name="control_header_text_size">20sp</dimen>
<dimen name="control_item_text_size">16sp</dimen>
<dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1334,4 +1345,47 @@
<!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
shade. -->
<dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+
+ <!-- Dream overlay complications related dimensions -->
+ <dimen name="dream_overlay_complication_clock_time_padding_left">50dp</dimen>
+ <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_padding_left">60dp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_padding_bottom">50dp</dimen>
+ <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen>
+ <dimen name="dream_overlay_complication_weather_padding_left">20dp</dimen>
+ <dimen name="dream_overlay_complication_weather_padding_bottom">50dp</dimen>
+ <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen>
+
+ <!-- The position of the end guide, which dream overlay complications can align their start with
+ if their end is aligned with the parent end. Represented as the percentage over from the
+ start of the parent container. -->
+ <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen">
+ 0.75
+ </item>
+
+ <!-- The position of the start guide, which dream overlay complications can align their end to
+ if their start is aligned with the parent start. Represented as the percentage over from
+ the start of the parent container. -->
+ <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen">
+ 0.25
+ </item>
+
+ <!-- The position of the bottom guide, which dream overlay complications can align their top to
+ if their bottom is aligned with the parent bottom. Represented as the percentage over from
+ the top of the parent container. -->
+ <item name="dream_overlay_complication_guide_bottom_percent" format="float" type="dimen">
+ 0.90
+ </item>
+
+ <!-- The position of the top guide, which dream overlay complications can align their bottom to
+ if their top is aligned with the parent top. Represented as the percentage over from
+ the top of the parent container. -->
+ <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen">
+ 0.10
+ </item>
+
+ <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. -->
+ <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen">
+ .2
+ </item>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6eab2b2..e5cabb0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -123,6 +123,18 @@
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+ <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
+ <string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
+
+ <!-- Description for the <Set Menu Language> confirmation dialog [CHAR LIMIT=NONE] -->
+ <string name="hdmi_cec_set_menu_language_description">System language change requested by another device</string>
+
+ <!-- Button label for accepting language change [CHAR LIMIT=25] -->
+ <string name="hdmi_cec_set_menu_language_accept">Change language</string>
+
+ <!-- Button label for declining language change [CHAR LIMIT=25] -->
+ <string name="hdmi_cec_set_menu_language_decline">Keep current language</string>
+
<!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
<string name="wifi_debugging_title">Allow wireless debugging on this network?</string>
@@ -2360,18 +2372,22 @@
<!-- 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 of the button to stop an app from running but the app is already stopped and the button is disabled [CHAR LIMIT=12]-->
+ <string name="fgs_manager_app_item_stop_button_stopped_label">Stopped</string>
<!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
<string name="clipboard_edit_text_copy">Copy</string>
<!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
<string name="clipboard_overlay_text_copied">Copied</string>
+ <!-- Text informing user where text being edited was copied from [CHAR LIMIT=NONE] -->
+ <string name="clipboard_edit_source">From <xliff:g id="appName" example="Gmail">%1$s</xliff:g></string>
<!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] -->
<string name="clipboard_dismiss_description">Dismiss copy UI</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac98739..590cc9b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -236,6 +236,41 @@
<item name="android:textColor">?android:attr/colorError</item>
</style>
+ <style name="TextAppearance.AuthNonBioCredential"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:accessibilityLiveRegion">polite</item>
+ <item name="android:textAlignment">gravity</item>
+ <item name="android:layout_gravity">top</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">36sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Error">
+ <item name="android:paddingTop">6dp</item>
+ <item name="android:paddingBottom">18dp</item>
+ <item name="android:paddingHorizontal">24dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/colorError</item>
+ <item name="android:gravity">center</item>
+ </style>
+
<style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
@@ -243,6 +278,15 @@
<item name="android:textSize">24sp</item>
</style>
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">28dp</item>
+ <item name="android:paddingBottom">20dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -307,9 +351,8 @@
<item name="android:maxWidth">420dp</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
- <item name="android:paddingBottom">0dp</item>
- <item name="android:paddingHorizontal">44dp</item>
- <item name="android:paddingTop">0dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingBottom">40dp</item>
</style>
<style name="LockPatternStyle">
@@ -404,19 +447,19 @@
</style>
<style name="DualToneLightTheme">
- <item name="backgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/light_mode_icon_color_single_tone</item>
<item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item>
</style>
<style name="DualToneDarkTheme">
- <item name="backgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item>
<item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item>
</style>
<style name="QSHeaderDarkTheme">
- <item name="backgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
+ <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item>
<item name="fillColor">@color/dark_mode_qs_icon_color_dual_tone_fill</item>
<item name="singleToneColor">@color/dark_mode_qs_icon_color_single_tone</item>
</style>
@@ -664,7 +707,9 @@
<item name="android:windowActivityTransitions">true</item>
</style>
- <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/>
+ <style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="overlayButtonTextColor">?android:attr/textColorPrimary</item>
+ </style>
<!-- Clipboard overlay's edit text activity. -->
<style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
new file mode 100644
index 0000000..ffab3cd
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.animation
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.lang.ref.WeakReference
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed, according to the
+ * direction specified in [ViewIdToTranslate.direction], for a maximum of [translationMax] when
+ * progresses are 0.
+ */
+class UnfoldConstantTranslateAnimator(
+ private val viewsIdToTranslate: Set<ViewIdToTranslate>,
+ private val progressProvider: UnfoldTransitionProgressProvider
+) : TransitionProgressListener {
+
+ private var viewsToTranslate = listOf<ViewToTranslate>()
+ private lateinit var rootView: ViewGroup
+ private var translationMax = 0f
+
+ fun init(rootView: ViewGroup, translationMax: Float) {
+ this.rootView = rootView
+ this.translationMax = translationMax
+ progressProvider.addCallback(this)
+ }
+
+ override fun onTransitionStarted() {
+ registerViewsForAnimation(rootView, viewsIdToTranslate)
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ translateViews(progress)
+ }
+
+ override fun onTransitionFinished() {
+ translateViews(progress = 1f)
+ }
+
+ private fun translateViews(progress: Float) {
+ // progress == 0 -> -translationMax
+ // progress == 1 -> 0
+ val xTrans = (progress - 1f) * translationMax
+ viewsToTranslate.forEach { (view, direction, shouldBeAnimated) ->
+ if (shouldBeAnimated()) {
+ view.get()?.translationX = xTrans * direction.multiplier
+ }
+ }
+ }
+
+ /** Finds in [parent] all views specified by [ids] and register them for the animation. */
+ private fun registerViewsForAnimation(parent: ViewGroup, ids: Set<ViewIdToTranslate>) {
+ viewsToTranslate =
+ ids.mapNotNull { (id, dir, pred) ->
+ parent.findViewById<View>(id)?.let { view ->
+ ViewToTranslate(WeakReference(view), dir, pred)
+ }
+ }
+ }
+
+ /** Represents a view to animate. [rootView] should contain a view with [viewId] inside. */
+ data class ViewIdToTranslate(
+ val viewId: Int,
+ val direction: Direction,
+ val shouldBeAnimated: () -> Boolean = { true }
+ )
+
+ private data class ViewToTranslate(
+ val view: WeakReference<View>,
+ val direction: Direction,
+ val shouldBeAnimated: () -> Boolean
+ )
+
+ /** Direction of the animation. */
+ enum class Direction(val multiplier: Float) {
+ LEFT(-1f),
+ RIGHT(1f),
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index 9010d51..fc6bb50 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -42,7 +42,9 @@
* are different than actual bounds (e.g. view container may
* have larger width than width of the items in the container)
*/
- private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
+ private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {},
+ /** Allows to set the alpha based on the progress. */
+ private val alphaProvider: AlphaProvider? = null
) : UnfoldTransitionProgressProvider.TransitionProgressListener {
private val screenSize = Point()
@@ -99,17 +101,27 @@
override fun onTransitionProgress(progress: Float) {
animatedViews.forEach {
- it.view.get()?.let { view ->
- translationApplier.apply(
- view = view,
- x = it.startTranslationX * (1 - progress),
- y = it.startTranslationY * (1 - progress)
- )
- }
+ it.applyTransition(progress)
+ it.applyAlpha(progress)
}
lastAnimationProgress = progress
}
+ private fun AnimatedView.applyTransition(progress: Float) {
+ view.get()?.let { view ->
+ translationApplier.apply(
+ view = view,
+ x = startTranslationX * (1 - progress),
+ y = startTranslationY * (1 - progress)
+ )
+ }
+ }
+
+ private fun AnimatedView.applyAlpha(progress: Float) {
+ if (alphaProvider == null) return
+ view.get()?.alpha = alphaProvider.getAlpha(progress)
+ }
+
private fun createAnimatedView(view: View): AnimatedView =
AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
@@ -146,6 +158,13 @@
}
}
+ /** Allows to set a custom alpha based on the progress. */
+ interface AlphaProvider {
+
+ /** Returns the alpha views should have at a given progress. */
+ fun getAlpha(progress: Float): Float
+ }
+
/**
* Interface that allows to use custom logic to get the center of the view
*/
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
deleted file mode 100644
index b76be4f..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.communal;
-
-import com.android.systemui.shared.communal.ICommunalSource;
-
-/**
-* An interface, implemented by SystemUI, for hosting a shared, communal surface on the lock
-* screen. Clients declare themselves sources (as defined by ICommunalSource). ICommunalHost is
-* meant only for the input of said sources. The lifetime scope and interactions that follow after
-* are bound to source.
-*/
-oneway interface ICommunalHost {
- /**
- * Invoked to specify the CommunalSource that should be consulted for communal surfaces to be
- * displayed.
- */
- void setSource(in ICommunalSource source) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
deleted file mode 100644
index 7ef403b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.communal;
-
-import com.android.systemui.shared.communal.ICommunalSurfaceCallback;
-
-/**
- * An interface, implemented by clients of CommunalHost, to provide communal surfaces for SystemUI.
- * The associated binder proxy will be retained by SystemUI and called on-demand when a communal
- * surface is needed (either new instantiation or update).
- */
-oneway interface ICommunalSource {
- /**
- * Called by the CommunalHost when a new communal surface is needed. The provided arguments
- * match the arguments necessary to construct a SurfaceControlViewHost for producing a
- * SurfacePackage to return.
- */
- void getCommunalSurface(in IBinder hostToken, in int width, in int height, in int displayId,
- in ICommunalSurfaceCallback callback) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
deleted file mode 100644
index 3d5998b..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.communal;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-/**
-* An interface for receiving the result of a surface request. ICommunalSurfaceCallback is
-* implemented by the CommunalHost (SystemUI) to process the results of a new communal surface.
-*/
-interface ICommunalSurfaceCallback {
- /**
- * Invoked when the CommunalSurface has generated the SurfacePackage to be displayed.
- */
- void onSurface(in SurfacePackage surfacePackage) = 1;
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
new file mode 100644
index 0000000..6db06f0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback;
+import com.android.systemui.shared.media.NearbyDevice;
+
+/**
+ * An interface that provides information about nearby devices that are able to play media.
+ *
+ * External clients will implement this interface and System UI will invoke it if it's passed to
+ * SystemUI via {@link INearbyMediaDevicesService.registerProvider}.
+ */
+interface INearbyMediaDevicesProvider {
+ /**
+ * Returns a list of nearby devices that are able to play media.
+ */
+ List<NearbyDevice> getCurrentNearbyDevices() = 1;
+
+ /**
+ * Registers a callback that will be notified each time the status of a nearby device changes.
+ */
+ oneway void registerNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 2;
+
+ /**
+ * Unregisters a callback. See {@link registerNearbyDevicesCallback}.
+ */
+ oneway void unregisterNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 3;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl
new file mode 100644
index 0000000..4f3e10d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.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 com.android.systemui.shared.media;
+
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider;
+
+/**
+ * An interface that can be invoked to notify System UI of nearby media devices.
+ *
+ * External clients wanting to notify System UI about the status of nearby media devices should
+ * implement {@link INearbyMediaDevicesProvider} and then register it with system UI using this
+ * service.
+ *
+ * System UI will implement this interface and external clients will invoke it.
+ */
+interface INearbyMediaDevicesService {
+ /** Registers a new provider. */
+ oneway void registerProvider(INearbyMediaDevicesProvider provider) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
new file mode 100644
index 0000000..a835f52
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media;
+
+/**
+ * A callback used to notify implementors of changes in the status of nearby devices that are able
+ * to play media.
+ *
+ * External clients may allow registration of these callbacks and external clients will be
+ * responsible for notifying the callbacks appropriately. System UI is only a mediator between the
+ * external client and these callbacks.
+ */
+interface INearbyMediaDevicesUpdateCallback {
+ /** Unknown distance range. */
+ const int RANGE_UNKNOWN = 0;
+ /** Distance is very far away from the peer device. */
+ const int RANGE_FAR = 1;
+ /** Distance is relatively long from the peer device, typically a few meters. */
+ const int RANGE_LONG = 2;
+ /** Distance is close to the peer device, typically with one or two meter. */
+ const int RANGE_CLOSE = 3;
+ /** Distance is very close to the peer device, typically within one meter or less. */
+ const int RANGE_WITHIN_REACH = 4;
+
+ /** Invoked by external clients when media device changes are detected. */
+ oneway void nearbyDeviceUpdate(in String routeId, in int rangeZone) = 1;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
similarity index 89%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
index 861a4ed..62b50ed 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package com.android.systemui.shared.media;
-parcelable DeviceInfo;
+parcelable NearbyDevice;
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
index 96b853f..9cab3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.nearby
+package com.android.systemui.shared.media
import android.os.Parcel
import android.os.Parcelable
@@ -26,14 +26,15 @@
* - [routeId] identifying the media route
* - [rangeZone] specifying how far away the device with the media route is from this device.
*/
-class NearbyDevice(parcel: Parcel) : Parcelable {
- var routeId: String? = null
+class NearbyDevice(
+ val routeId: String?,
@RangeZone val rangeZone: Int
+) : Parcelable {
- init {
- routeId = parcel.readString() ?: "unknown"
+ private constructor(parcel: Parcel) : this(
+ routeId = parcel.readString() ?: null,
rangeZone = parcel.readInt()
- }
+ )
override fun describeContents() = 0
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
new file mode 100644
index 0000000..b5eaff6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.media
+
+import androidx.annotation.IntDef
+import kotlin.annotation.AnnotationRetention
+
+@IntDef(
+ INearbyMediaDevicesUpdateCallback.RANGE_UNKNOWN,
+ INearbyMediaDevicesUpdateCallback.RANGE_FAR,
+ INearbyMediaDevicesUpdateCallback.RANGE_LONG,
+ INearbyMediaDevicesUpdateCallback.RANGE_CLOSE,
+ INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+)
+@Retention(AnnotationRetention.SOURCE)
+/** The various range zones a device can be in, in relation to the current device. */
+annotation class RangeZone
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
deleted file mode 100644
index d41aaf3..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.mediattt
-
-import android.os.Parcel
-import android.os.Parcelable
-
-/**
- * Represents a device that can send or receive media. Includes any device information necessary for
- * SysUI to display an informative chip to the user.
- */
-class DeviceInfo(val name: String) : Parcelable {
- constructor(parcel: Parcel) : this(parcel.readString())
-
- override fun writeToParcel(dest: Parcel?, flags: Int) {
- dest?.writeString(name)
- }
-
- override fun describeContents() = 0
-
- override fun toString() = "name: $name"
-
- companion object CREATOR : Parcelable.Creator<DeviceInfo> {
- override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel)
- override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size)
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
deleted file mode 100644
index eb1c9d0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.mediattt;
-
-import android.media.MediaRoute2Info;
-import com.android.systemui.shared.mediattt.DeviceInfo;
-import com.android.systemui.shared.mediattt.IUndoTransferCallback;
-
-/**
- * An interface that can be invoked to trigger media transfer events on System UI.
- *
- * This interface is for the *sender* device, which is the device currently playing media. This
- * sender device can transfer the media to a different device, called the receiver.
- *
- * System UI will implement this interface and other services will invoke it.
- */
-interface IDeviceSenderService {
- /**
- * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
- * the user can potentially *start* a cast to the receiver device if the user moves their device
- * a bit closer.
- *
- * Important notes:
- * - When this callback triggers, the device is close enough to inform the user that
- * transferring is an option, but the device is *not* close enough to actually initiate a
- * transfer yet.
- * - This callback is for *starting* a cast. It should be used when this device is currently
- * playing media locally and the media should be transferred to be played on the receiver
- * device instead.
- */
- oneway void closeToReceiverToStartCast(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
- /**
- * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
- * the user can potentially *end* a cast on the receiver device if the user moves this device a
- * bit closer.
- *
- * Important notes:
- * - When this callback triggers, the device is close enough to inform the user that
- * transferring is an option, but the device is *not* close enough to actually initiate a
- * transfer yet.
- * - This callback is for *ending* a cast. It should be used when media is currently being
- * played on the receiver device and the media should be transferred to play locally
- * instead.
- */
- oneway void closeToReceiverToEndCast(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
- /**
- * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
- * device has been started.
- *
- * Important notes:
- * - This callback is for *starting* a cast. It should be used when this device is currently
- * playing media locally and the media has started being transferred to the receiver device
- * instead.
- */
- oneway void transferToReceiverTriggered(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
- /**
- * Invoke to notify System UI that a media transfer from the receiver and back to this device
- * (the sender) has been started.
- *
- * Important notes:
- * - This callback is for *ending* a cast. It should be used when media is currently being
- * played on the receiver device and the media has started being transferred to play locally
- * instead.
- */
- oneway void transferToThisDeviceTriggered(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
- /**
- * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
- * device has finished successfully.
- *
- * Important notes:
- * - This callback is for *starting* a cast. It should be used when this device had previously
- * been playing media locally and the media has successfully been transferred to the
- * receiver device instead.
- *
- * @param undoCallback will be invoked if the user chooses to undo this transfer.
- */
- oneway void transferToReceiverSucceeded(
- in MediaRoute2Info mediaInfo,
- in DeviceInfo otherDeviceInfo,
- in IUndoTransferCallback undoCallback);
-
- /**
- * Invoke to notify System UI that a media transfer from the receiver and back to this device
- * (the sender) has finished successfully.
- *
- * Important notes:
- * - This callback is for *ending* a cast. It should be used when media was previously being
- * played on the receiver device and has been successfully transferred to play locally on
- * this device instead.
- *
- * @param undoCallback will be invoked if the user chooses to undo this transfer.
- */
- oneway void transferToThisDeviceSucceeded(
- in MediaRoute2Info mediaInfo,
- in DeviceInfo otherDeviceInfo,
- in IUndoTransferCallback undoCallback);
-
- /**
- * Invoke to notify System UI that the attempted transfer has failed.
- *
- * This callback will be used for both the transfer that should've *started* playing the media
- * on the receiver and the transfer that should've *ended* the playing on the receiver.
- */
- oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-
- /**
- * Invoke to notify System UI that this device is no longer close to the receiver device.
- */
- oneway void noLongerCloseToReceiver(
- in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 5d092d0..eebc791 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -120,6 +120,8 @@
public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
// The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+ // The current app is in immersive mode
+ public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -145,7 +147,8 @@
SYSUI_STATE_IME_SWITCHER_SHOWING,
SYSUI_STATE_DEVICE_DOZING,
SYSUI_STATE_BACK_DISABLED,
- SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
+ SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ SYSUI_STATE_IMMERSIVE_MODE
})
public @interface SystemUiStateFlags {}
@@ -179,6 +182,7 @@
str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
? "bubbles_mange_menu_expanded" : "");
+ str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
return str.toString();
}
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/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index 214b284..43cd764 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -16,6 +16,7 @@
package com.android.keyguard
+import android.app.StatusBarManager.SESSION_KEYGUARD
import android.content.Context
import android.hardware.biometrics.BiometricSourceType
import com.android.internal.annotations.VisibleForTesting
@@ -28,7 +29,7 @@
import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
@@ -44,7 +45,7 @@
context: Context?,
private val uiEventLogger: UiEventLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dumpManager: DumpManager
+ private val sessionTracker: SessionTracker
) : CoreStartable(context) {
private var fingerprintLockedOut = false
private var faceLockedOut = false
@@ -53,7 +54,6 @@
private var timeout = false
override fun start() {
- dumpManager.registerDumpable(this)
mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
KeyguardUpdateMonitor.getCurrentUser())
keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
@@ -65,22 +65,17 @@
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
if (lockedOut && !fingerprintLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
} else if (!lockedOut && fingerprintLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
}
fingerprintLockedOut = lockedOut
} else if (biometricSourceType == BiometricSourceType.FACE) {
val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
if (lockedOut && !faceLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
} else if (!lockedOut && faceLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
}
faceLockedOut = lockedOut
}
@@ -95,20 +90,19 @@
val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
if (newEncryptedOrLockdown && !encryptedOrLockdown) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
}
encryptedOrLockdown = newEncryptedOrLockdown
val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
if (newUnattendedUpdate && !unattendedUpdate) {
- uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
}
unattendedUpdate = newUnattendedUpdate
val newTimeout = isStrongAuthTimeout(strongAuthFlags)
if (newTimeout && !timeout) {
- uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
}
timeout = newTimeout
}
@@ -123,6 +117,9 @@
) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
+ private fun log(event: PrimaryAuthRequiredEvent) =
+ uiEventLogger.log(event, sessionTracker.getSessionId(SESSION_KEYGUARD))
+
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
pw.println(" mFingerprintLockedOut=$fingerprintLockedOut")
pw.println(" mFaceLockedOut=$faceLockedOut")
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/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 49a8022..57997d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -36,7 +38,10 @@
import android.util.Slog;
import android.view.MotionEvent;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -53,6 +58,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -86,6 +92,7 @@
private final UserSwitcherController mUserSwitcherController;
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
+ private final SessionTracker mSessionTracker;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -191,7 +198,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
- : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
+ : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
}
public void reset() {
@@ -242,7 +249,8 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ SessionTracker sessionTracker) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -261,6 +269,7 @@
mUserSwitcherController = userSwitcherController;
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
+ mSessionTracker = sessionTracker;
}
@Override
@@ -456,7 +465,7 @@
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
}
if (uiEvent != BouncerUiEvent.UNKNOWN) {
- mUiEventLogger.log(uiEvent);
+ mUiEventLogger.log(uiEvent, getSessionId());
}
if (finish) {
mSecurityCallback.finish(strongAuth, targetUserId);
@@ -599,6 +608,10 @@
}
}
+ private @Nullable InstanceId getSessionId() {
+ return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ }
+
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
mView.updatePositionByTouchX(x);
@@ -622,6 +635,7 @@
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
private final UserSwitcherController mUserSwitcherController;
+ private final SessionTracker mSessionTracker;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -639,7 +653,8 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ SessionTracker sessionTracker) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -655,6 +670,7 @@
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
mUserSwitcherController = userSwitcherController;
+ mSessionTracker = sessionTracker;
}
public KeyguardSecurityContainerController create(
@@ -664,7 +680,7 @@
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings);
+ mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index cb25e1a..89d6fb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -17,11 +17,13 @@
package com.android.keyguard
import android.content.Context
-import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
import com.android.systemui.unfold.SysUIUnfoldScope
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import javax.inject.Inject
@@ -30,84 +32,37 @@
* the set of ids, which also dictact which direction to move and when, via a filter function.
*/
@SysUIUnfoldScope
-class KeyguardUnfoldTransition @Inject constructor(
- val context: Context,
- val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+class KeyguardUnfoldTransition
+@Inject
+constructor(
+ private val context: Context,
+ unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
) {
- companion object {
- final val LEFT = -1
- final val RIGHT = 1
- }
+ /** Certain views only need to move if they are not currently centered */
+ var statusViewCentered = false
private val filterSplitShadeOnly = { !statusViewCentered }
private val filterNever = { true }
- private val ids = setOf(
- Triple(R.id.keyguard_status_area, LEFT, filterNever),
- Triple(R.id.controls_button, LEFT, filterNever),
- Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
- Triple(R.id.lockscreen_clock_view, LEFT, filterNever),
- Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- Triple(R.id.wallet_button, RIGHT, filterNever)
- )
- private var parent: ViewGroup? = null
- private var views = listOf<Triple<View, Int, () -> Boolean>>()
- private var xTranslationMax = 0f
-
- /**
- * Certain views only need to move if they are not currently centered
- */
- var statusViewCentered = false
-
- init {
- unfoldProgressProvider.addCallback(
- object : TransitionProgressListener {
- override fun onTransitionStarted() {
- findViews()
- }
-
- override fun onTransitionProgress(progress: Float) {
- translateViews(progress)
- }
-
- override fun onTransitionFinished() {
- translateViews(1f)
- }
- }
- )
+ private val translateAnimator by lazy {
+ UnfoldConstantTranslateAnimator(
+ viewsIdToTranslate =
+ setOf(
+ ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever),
+ ViewIdToTranslate(R.id.controls_button, LEFT, filterNever),
+ ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
+ ViewIdToTranslate(
+ R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
+ ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+ progressProvider = unfoldProgressProvider)
}
- /**
- * Relies on the [parent] to locate views to translate
- */
+ /** Relies on the [parent] to locate views to translate. */
fun setup(parent: ViewGroup) {
- this.parent = parent
- xTranslationMax = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_unfold_translation_x).toFloat()
- }
-
- /**
- * Manually translate views based on set direction. At the moment
- * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance
- * from their mid-point. This code instead will only ever translate by a fixed amount.
- */
- private fun translateViews(progress: Float) {
- val xTrans = progress * xTranslationMax - xTranslationMax
- views.forEach {
- (view, direction, pred) -> if (pred()) {
- view.setTranslationX(xTrans * direction)
- }
- }
- }
-
- private fun findViews() {
- parent?.let { p ->
- views = ids.mapNotNull {
- (id, direction, pred) -> p.findViewById<View>(id)?.let {
- Triple(it, direction, pred)
- }
- }
- }
+ val translationMax =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
+ translateAnimator.init(parent, translationMax)
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f2d0427..d7a8a7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -680,6 +680,13 @@
}
/**
+ * Whether the secure camera is currently showing over the keyguard.
+ */
+ public boolean isSecureCameraLaunchedOverKeyguard() {
+ return mSecureCameraLaunched;
+ }
+
+ /**
* @return a cached version of DreamManager.isDreaming()
*/
public boolean isDreaming() {
@@ -1196,6 +1203,21 @@
return fingerprintAllowed || faceAllowed;
}
+ /**
+ * Returns whether the user is unlocked with a biometric that is currently bypassing
+ * the lock screen.
+ */
+ public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) {
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+ // fingerprint always bypasses
+ boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+ && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+ boolean faceAllowed = face != null && face.mAuthenticated
+ && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+ return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass();
+ }
+
public boolean getUserTrustIsManaged(int userId) {
return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 6626f59..80a3a0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -35,7 +35,6 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Process;
import android.os.VibrationAttributes;
-import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
@@ -60,6 +59,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -103,7 +103,7 @@
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
- @Nullable private final Vibrator mVibrator;
+ @NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@@ -154,7 +154,7 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@Nullable AuthRippleController authRippleController,
@NonNull @Main Resources resources
) {
@@ -560,7 +560,7 @@
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_HOVER_ENTER:
- if (mVibrator != null && !mDownDetected) {
+ if (!mDownDetected) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -647,15 +647,13 @@
mOnGestureDetectedRunnable.run();
}
- if (mVibrator != null) {
- // play device entry haptic (same as biometric success haptic)
- mVibrator.vibrate(
- Process.myUid(),
- getContext().getOpPackageName(),
- UdfpsController.EFFECT_CLICK,
- "lock-icon-device-entry",
- TOUCH_VIBRATION_ATTRIBUTES);
- }
+ // play device entry haptic (same as biometric success haptic)
+ mVibrator.vibrate(
+ Process.myUid(),
+ getContext().getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "lock-icon-device-entry",
+ TOUCH_VIBRATION_ATTRIBUTES);
mKeyguardViewController.showBouncer(/* scrim */ true);
}
@@ -670,12 +668,9 @@
mVelocityTracker.recycle();
mVelocityTracker = null;
}
- if (mVibrator != null) {
- mVibrator.cancel();
- }
+ mVibrator.cancel();
}
-
private boolean inLockIconArea(MotionEvent event) {
return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& mView.getVisibility() == View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 08ed24c..b32c2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,6 +49,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -250,6 +251,7 @@
@Inject Lazy<LocationController> mLocationController;
@Inject Lazy<RotationLockController> mRotationLockController;
@Inject Lazy<ZenModeController> mZenModeController;
+ @Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper;
@Inject Lazy<HotspotController> mHotspotController;
@Inject Lazy<CastController> mCastController;
@Inject Lazy<FlashlightController> mFlashlightController;
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index fdc3229..2b8d3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -54,11 +54,11 @@
Utils.getThemeAttr(context, R.attr.lightIconTheme))
darkColor = Color(
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
- Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.backgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
lightColor = Color(
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
- Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.backgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
}
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 0b967b7..f00615b 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -23,6 +23,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
@@ -69,6 +70,19 @@
// 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
private static final float STRETCH_INTERVAL = 2f;
+ private static final FloatProperty<ViewScaler> VIEW_SCALER_HEIGHT_PROPERTY =
+ new FloatProperty<ViewScaler>("ViewScalerHeight") {
+ @Override
+ public void setValue(ViewScaler object, float value) {
+ object.setHeight(value);
+ }
+
+ @Override
+ public Float get(ViewScaler object) {
+ return object.getHeight();
+ }
+ };
+
@SuppressWarnings("unused")
private Context mContext;
@@ -147,6 +161,7 @@
public void setView(ExpandableView v) {
mView = v;
}
+
public void setHeight(float h) {
if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
mView.setActualHeight((int) h);
@@ -176,7 +191,7 @@
mCallback = callback;
mScaler = new ViewScaler();
mGravity = Gravity.TOP;
- mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
+ mScaleAnimation = ObjectAnimator.ofFloat(mScaler, VIEW_SCALER_HEIGHT_PROPERTY, 0f);
mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
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/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 20d6e32..881e6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -120,6 +120,13 @@
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
/**
+ * Action ID to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+ * play/stop media
+ */
+ private static final int SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK =
+ AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK; // = 10
+
+ /**
* Action ID to trigger the accessibility button
*/
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
@@ -137,6 +144,36 @@
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
+ /**
+ * Action ID to trigger the dpad up button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_UP =
+ AccessibilityService.GLOBAL_ACTION_DPAD_UP; // 16
+
+ /**
+ * Action ID to trigger the dpad down button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_DOWN =
+ AccessibilityService.GLOBAL_ACTION_DPAD_DOWN; // 17
+
+ /**
+ * Action ID to trigger the dpad left button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_LEFT =
+ AccessibilityService.GLOBAL_ACTION_DPAD_LEFT; // 18
+
+ /**
+ * Action ID to trigger the dpad right button
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_RIGHT =
+ AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT; // 19
+
+ /**
+ * Action ID to trigger dpad center keyevent
+ */
+ private static final int SYSTEM_ACTION_ID_DPAD_CENTER =
+ AccessibilityService.GLOBAL_ACTION_DPAD_CENTER; // 20
+
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final SystemActionsBroadcastReceiver mReceiver;
@@ -222,10 +259,34 @@
R.string.accessibility_system_action_screenshot_label,
SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
+ RemoteAction actionHeadsetHook = createRemoteAction(
+ R.string.accessibility_system_action_headset_hook_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK);
+
RemoteAction actionAccessibilityShortcut = createRemoteAction(
R.string.accessibility_system_action_hardware_a11y_shortcut_label,
SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
+ RemoteAction actionDpadUp = createRemoteAction(
+ R.string.accessibility_system_action_dpad_up_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP);
+
+ RemoteAction actionDpadDown = createRemoteAction(
+ R.string.accessibility_system_action_dpad_down_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN);
+
+ RemoteAction actionDpadLeft = createRemoteAction(
+ R.string.accessibility_system_action_dpad_left_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT);
+
+ RemoteAction actionDpadRight = createRemoteAction(
+ R.string.accessibility_system_action_dpad_right_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT);
+
+ RemoteAction actionDpadCenter = createRemoteAction(
+ R.string.accessibility_system_action_dpad_center_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER);
+
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
@@ -234,8 +295,14 @@
mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
+ mA11yManager.registerSystemAction(actionHeadsetHook, SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK);
mA11yManager.registerSystemAction(
actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+ mA11yManager.registerSystemAction(actionDpadUp, SYSTEM_ACTION_ID_DPAD_UP);
+ mA11yManager.registerSystemAction(actionDpadDown, SYSTEM_ACTION_ID_DPAD_DOWN);
+ mA11yManager.registerSystemAction(actionDpadLeft, SYSTEM_ACTION_ID_DPAD_LEFT);
+ mA11yManager.registerSystemAction(actionDpadRight, SYSTEM_ACTION_ID_DPAD_RIGHT);
+ mA11yManager.registerSystemAction(actionDpadCenter, SYSTEM_ACTION_ID_DPAD_CENTER);
registerOrUnregisterDismissNotificationShadeAction();
}
@@ -305,6 +372,10 @@
labelId = R.string.accessibility_system_action_screenshot_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
break;
+ case SYSTEM_ACTION_ID_KEYCODE_HEADSETHOOK:
+ labelId = R.string.accessibility_system_action_headset_hook_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HEADSET_HOOK;
+ break;
case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
labelId = R.string.accessibility_system_action_on_screen_a11y_shortcut_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
@@ -323,6 +394,26 @@
intent = SystemActionsBroadcastReceiver
.INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
break;
+ case SYSTEM_ACTION_ID_DPAD_UP:
+ labelId = R.string.accessibility_system_action_dpad_up_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_UP;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_DOWN:
+ labelId = R.string.accessibility_system_action_dpad_down_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_DOWN;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_LEFT:
+ labelId = R.string.accessibility_system_action_dpad_left_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_LEFT;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_RIGHT:
+ labelId = R.string.accessibility_system_action_dpad_right_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_RIGHT;
+ break;
+ case SYSTEM_ACTION_ID_DPAD_CENTER:
+ labelId = R.string.accessibility_system_action_dpad_center_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER;
+ break;
default:
return;
}
@@ -411,6 +502,10 @@
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
+ private void handleHeadsetHook() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+ }
+
private void handleAccessibilityButton() {
AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
Display.DEFAULT_DISPLAY);
@@ -434,6 +529,26 @@
CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
}
+ private void handleDpadUp() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+ }
+
+ private void handleDpadDown() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ private void handleDpadLeft() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+ }
+
+ private void handleDpadRight() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+ }
+
+ private void handleDpadCenter() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -443,6 +558,7 @@
private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
+ private static final String INTENT_ACTION_HEADSET_HOOK = "SYSTEM_ACTION_HEADSET_HOOK";
private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
"SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
@@ -451,6 +567,11 @@
"SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
"SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
+ private static final String INTENT_ACTION_DPAD_UP = "SYSTEM_ACTION_DPAD_UP";
+ private static final String INTENT_ACTION_DPAD_DOWN = "SYSTEM_ACTION_DPAD_DOWN";
+ private static final String INTENT_ACTION_DPAD_LEFT = "SYSTEM_ACTION_DPAD_LEFT";
+ private static final String INTENT_ACTION_DPAD_RIGHT = "SYSTEM_ACTION_DPAD_RIGHT";
+ private static final String INTENT_ACTION_DPAD_CENTER = "SYSTEM_ACTION_DPAD_CENTER";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -462,10 +583,16 @@
case INTENT_ACTION_POWER_DIALOG:
case INTENT_ACTION_LOCK_SCREEN:
case INTENT_ACTION_TAKE_SCREENSHOT:
+ case INTENT_ACTION_HEADSET_HOOK:
case INTENT_ACTION_ACCESSIBILITY_BUTTON:
case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
- case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+ case INTENT_ACTION_DPAD_UP:
+ case INTENT_ACTION_DPAD_DOWN:
+ case INTENT_ACTION_DPAD_LEFT:
+ case INTENT_ACTION_DPAD_RIGHT:
+ case INTENT_ACTION_DPAD_CENTER: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
return PendingIntent.getBroadcast(context, 0, intent,
@@ -487,10 +614,16 @@
intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
+ intentFilter.addAction(INTENT_ACTION_HEADSET_HOOK);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ intentFilter.addAction(INTENT_ACTION_DPAD_UP);
+ intentFilter.addAction(INTENT_ACTION_DPAD_DOWN);
+ intentFilter.addAction(INTENT_ACTION_DPAD_LEFT);
+ intentFilter.addAction(INTENT_ACTION_DPAD_RIGHT);
+ intentFilter.addAction(INTENT_ACTION_DPAD_CENTER);
return intentFilter;
}
@@ -530,6 +663,10 @@
handleTakeScreenshot();
break;
}
+ case INTENT_ACTION_HEADSET_HOOK: {
+ handleHeadsetHook();
+ break;
+ }
case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
handleAccessibilityButton();
break;
@@ -546,6 +683,26 @@
handleAccessibilityDismissNotificationShade();
break;
}
+ case INTENT_ACTION_DPAD_UP: {
+ handleDpadUp();
+ break;
+ }
+ case INTENT_ACTION_DPAD_DOWN: {
+ handleDpadDown();
+ break;
+ }
+ case INTENT_ACTION_DPAD_LEFT: {
+ handleDpadLeft();
+ break;
+ }
+ case INTENT_ACTION_DPAD_RIGHT: {
+ handleDpadRight();
+ break;
+ }
+ case INTENT_ACTION_DPAD_CENTER: {
+ handleDpadCenter();
+ break;
+ }
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index fd37b35..21edb24 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -52,7 +52,6 @@
import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -106,7 +105,7 @@
private final float mTranslationY;
- @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
@VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
@@ -187,10 +186,12 @@
public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+ @Nullable List<FaceSensorPropertiesInternal> faceProps,
+ WakefulnessLifecycle wakefulnessLifecycle) {
mConfig.mSensorIds = sensorIds;
mConfig.mCredentialAllowed = credentialAllowed;
- return new AuthContainerView(mConfig, new Injector(), fpProps, faceProps);
+ return new AuthContainerView(
+ mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle);
}
}
@@ -276,7 +277,8 @@
@VisibleForTesting
AuthContainerView(Config config, Injector injector,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+ @Nullable List<FaceSensorPropertiesInternal> faceProps,
+ WakefulnessLifecycle wakefulnessLifecycle) {
super(config.mContext);
mConfig = config;
@@ -289,7 +291,7 @@
mHandler = new Handler(Looper.getMainLooper());
mWindowManager = mContext.getSystemService(WindowManager.class);
- mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+ mWakefulnessLifecycle = wakefulnessLifecycle;
mTranslationY = getResources()
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 2b12f67..fe5e36e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -37,6 +37,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
@@ -63,6 +64,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.Execution;
@@ -95,6 +98,7 @@
private final Handler mHandler;
private final Execution mExecution;
private final CommandQueue mCommandQueue;
+ private final StatusBarStateController mStatusBarStateController;
private final ActivityTaskManager mActivityTaskManager;
@Nullable
private final FingerprintManager mFingerprintManager;
@@ -117,6 +121,7 @@
@Nullable private UdfpsController mUdfpsController;
@Nullable private IUdfpsHbmListener mUdfpsHbmListener;
@Nullable private SidefpsController mSidefpsController;
+ @Nullable private IBiometricContextListener mBiometricContextListener;
@VisibleForTesting
TaskStackListener mTaskStackListener;
@VisibleForTesting
@@ -129,7 +134,8 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
- private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -168,6 +174,10 @@
mCurrentDialog = null;
mOrientationListener.disable();
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
try {
if (mReceiver != null) {
mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -198,6 +208,10 @@
mCurrentDialog = null;
mOrientationListener.disable();
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
if (mReceiver != null) {
mReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -460,6 +474,7 @@
Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
return;
}
+
try {
mReceiver.onDialogDismissed(reason, credentialAttestation);
} catch (RemoteException e) {
@@ -479,9 +494,12 @@
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ @NonNull StatusBarStateController statusBarStateController,
@Main Handler handler) {
super(context);
mExecution = execution;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
mHandler = handler;
mCommandQueue = commandQueue;
mActivityTaskManager = activityTaskManager;
@@ -491,6 +509,7 @@
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
@@ -501,6 +520,14 @@
return Unit.INSTANCE;
});
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ notifyDozeChanged(isDozing);
+ }
+ });
+
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
int[] faceAuthLocation = context.getResources().getIntArray(
@@ -551,6 +578,22 @@
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
}
+ @Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ mBiometricContextListener = listener;
+ notifyDozeChanged(mStatusBarStateController.isDozing());
+ }
+
+ private void notifyDozeChanged(boolean isDozing) {
+ if (mBiometricContextListener != null) {
+ try {
+ mBiometricContextListener.onDozeChanged(isDozing);
+ } catch (RemoteException e) {
+ Log.w(TAG, "failed to notify initial doze state");
+ }
+ }
+ }
+
/**
* Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
*
@@ -788,7 +831,8 @@
skipAnimation,
operationId,
requestId,
- multiSensorConfig);
+ multiSensorConfig,
+ mWakefulnessLifecycle);
if (newDialog == null) {
Log.e(TAG, "Unsupported type configuration");
@@ -811,6 +855,9 @@
}
mReceiver = (IBiometricSysuiReceiver) args.arg2;
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptShown();
+ }
mCurrentDialog = newDialog;
mCurrentDialog.show(mWindowManager, savedState);
mOrientationListener.enable();
@@ -821,6 +868,11 @@
if (mCurrentDialog == null) {
Log.w(TAG, "Dialog already dismissed");
}
+
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
mReceiver = null;
mCurrentDialog = null;
mOrientationListener.disable();
@@ -868,7 +920,8 @@
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
boolean skipIntro, long operationId, long requestId,
- @BiometricMultiSensorMode int multiSensorConfig) {
+ @BiometricMultiSensorMode int multiSensorConfig,
+ WakefulnessLifecycle wakefulnessLifecycle) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setPromptInfo(promptInfo)
@@ -879,7 +932,7 @@
.setOperationId(operationId)
.setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
- .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
+ .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle);
}
/**
@@ -891,12 +944,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/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5ddfd75..8052c20 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -42,7 +42,6 @@
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -64,6 +63,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -117,7 +117,7 @@
@NonNull private final DumpManager mDumpManager;
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Nullable private final Vibrator mVibrator;
+ @NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -506,7 +506,7 @@
@NonNull AccessibilityManager accessibilityManager,
@NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController,
@NonNull ScreenLifecycle screenLifecycle,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
@NonNull Optional<UdfpsHbmProvider> hbmProvider,
@NonNull KeyguardStateController keyguardStateController,
@@ -577,14 +577,12 @@
*/
@VisibleForTesting
public void playStartHaptic() {
- if (mVibrator != null) {
- mVibrator.vibrate(
- Process.myUid(),
- mContext.getOpPackageName(),
- EFFECT_CLICK,
- "udfps-onStart-click",
- VIBRATION_ATTRIBUTES);
- }
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ EFFECT_CLICK,
+ "udfps-onStart-click",
+ VIBRATION_ATTRIBUTES);
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index e231310..eaee19a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -18,16 +18,12 @@
import android.media.AudioAttributes
import android.os.VibrationEffect
-import android.os.Vibrator
-
import com.android.keyguard.KeyguardUpdateMonitor
-
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
-
import java.io.PrintWriter
-
import javax.inject.Inject
/**
@@ -36,7 +32,7 @@
@SysUISingleton
class UdfpsHapticsSimulator @Inject constructor(
commandRegistry: CommandRegistry,
- val vibrator: Vibrator?,
+ val vibrator: VibratorHelper,
val keyguardUpdateMonitor: KeyguardUpdateMonitor
) : Command {
val sonificationEffects =
@@ -60,13 +56,13 @@
}
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
- vibrator?.vibrate(
+ vibrator.vibrate(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
sonificationEffects)
}
"error" -> {
// needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT
- vibrator?.vibrate(
+ vibrator.vibrate(
VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
sonificationEffects)
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 41a4963..54664f2 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -48,7 +48,7 @@
@Override
public void start() {
if (DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
mClipboardManager.addPrimaryClipChangedListener(this);
}
@@ -60,10 +60,11 @@
return;
}
if (mClipboardOverlayController == null) {
- mClipboardOverlayController = new ClipboardOverlayController(mContext,
- new TimeoutHandler(mContext));
+ mClipboardOverlayController =
+ new ClipboardOverlayController(mContext, new TimeoutHandler(mContext));
}
- mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+ mClipboardOverlayController.setClipData(
+ mClipboardManager.getPrimaryClip(), mClipboardManager.getPrimaryClipSource());
mClipboardOverlayController.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlayController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index caf0307..f6d6464 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,17 +62,23 @@
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;
import com.android.systemui.R;
import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.ScreenshotActionChip;
+import com.android.systemui.screenshot.OverlayActionChip;
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,17 +101,23 @@
private final PhoneWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final AccessibilityManager mAccessibilityManager;
+ private final TextClassifier mTextClassifier;
+ private final FrameLayout mContainer;
private final DraggableConstraintLayout mView;
private final ImageView mImagePreview;
private final TextView mTextPreview;
- private final ScreenshotActionChip mEditChip;
- private final ScreenshotActionChip mRemoteCopyChip;
+ private final OverlayActionChip mEditChip;
+ private final OverlayActionChip mRemoteCopyChip;
private final View mActionContainerBackground;
private final View mDismissButton;
+ private final LinearLayout mActionContainer;
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
private Runnable mOnSessionCompleteListener;
+
+ private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
private BroadcastReceiver mCloseDialogsReceiver;
@@ -117,6 +131,8 @@
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+ .getTextClassifier();
mWindowManager = mContext.getSystemService(WindowManager.class);
@@ -132,10 +148,12 @@
mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
- mView = (DraggableConstraintLayout)
+ mContainer = (FrameLayout)
LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
- mActionContainerBackground = requireNonNull(
- mView.findViewById(R.id.actions_container_background));
+ mView = requireNonNull(mContainer.findViewById(R.id.clipboard_ui));
+ 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 +161,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());
@@ -164,9 +182,10 @@
attachWindow();
withWindowAttached(() -> {
- mWindow.setContentView(mView);
+ mWindow.setContentView(mContainer);
updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
- mView.post(() -> getEnterAnimation().start());
+ mView.requestLayout();
+ mView.post(this::animateIn);
});
mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
@@ -197,14 +216,17 @@
mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
}
- void setClipData(ClipData clipData) {
+ void setClipData(ClipData clipData, String clipSource) {
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), clipSource));
+ }
+ showEditableText(item.getText());
} else if (clipData.getItemAt(0).getUri() != null) {
// How to handle non-image URIs?
showEditableImage(clipData.getItemAt(0).getUri());
@@ -212,7 +234,6 @@
showTextPreview(
mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
}
-
mTimeoutHandler.resetTimeout();
}
@@ -220,10 +241,40 @@
mOnSessionCompleteListener = runnable;
}
+ private void classifyText(ClipData.Item item, String source) {
+ 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(() -> {
+ resetActionChips();
+ for (RemoteAction action : actions) {
+ Intent targetIntent = action.getActionIntent().getIntent();
+ if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) {
+ OverlayActionChip chip = constructActionChip(action);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ }
+ }
+ });
+ }
+
+ private OverlayActionChip constructActionChip(RemoteAction action) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_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) {
@@ -292,7 +343,7 @@
mEditChip.setAlpha(1f);
ContentResolver resolver = mContext.getContentResolver();
try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
// The width of the view is capped, height maintains aspect ratio, so allow it to be
// taller if needed.
Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
@@ -311,14 +362,18 @@
return nearbyIntent;
}
+ private void animateIn() {
+ getEnterAnimation().start();
+ }
+
private void animateOut() {
- getExitAnimation().start();
+ mView.dismiss();
}
private ValueAnimator getEnterAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- mView.setAlpha(0);
+ mContainer.setAlpha(0);
mDismissButton.setVisibility(View.GONE);
final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
final View actionBackground = requireNonNull(
@@ -330,7 +385,7 @@
}
anim.addUpdateListener(animation -> {
- mView.setAlpha(animation.getAnimatedFraction());
+ mContainer.setAlpha(animation.getAnimatedFraction());
float scale = 0.6f + 0.4f * animation.getAnimatedFraction();
mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f);
mView.setPivotX(actionBackground.getWidth() / 2f);
@@ -341,35 +396,13 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mView.setAlpha(1);
+ mContainer.setAlpha(1);
mTimeoutHandler.resetTimeout();
}
});
return anim;
}
- private ValueAnimator getExitAnimation() {
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
- anim.addUpdateListener(animation -> {
- mView.setAlpha(1 - animation.getAnimatedFraction());
- final View actionBackground = requireNonNull(
- mView.findViewById(R.id.actions_container_background));
- mView.setTranslationX(
- -animation.getAnimatedFraction() * actionBackground.getWidth() / 2);
- });
-
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- hideImmediate();
- }
- });
-
- return anim;
- }
-
private void hideImmediate() {
// Note this may be called multiple times if multiple dismissal events happen at the same
// time.
@@ -390,14 +423,26 @@
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
if (mOnSessionCompleteListener != null) {
mOnSessionCompleteListener.run();
}
}
+ private void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ }
+
private void reset() {
mView.setTranslationX(0);
- mView.setAlpha(0);
+ mContainer.setAlpha(0);
+ resetActionChips();
mTimeoutHandler.cancelTimeout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
index 6a4be6e..8843462 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -98,10 +98,23 @@
return mSwipeDetector.onTouchEvent(ev);
}
+ /**
+ * Dismiss the view, with animation controlled by SwipeDismissHandler
+ */
+ public void dismiss() {
+ mSwipeDismissHandler.dismiss();
+ }
+
+ /**
+ * Set the callback to be run after view is dismissed
+ */
public void setOnDismissCallback(Runnable callback) {
mOnDismiss = callback;
}
+ /**
+ * Set the callback to be run when the view is interacted with (e.g. tapped)
+ */
public void setOnInteractionCallback(Runnable callback) {
mOnInteraction = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index be10c35..a57a135 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -22,9 +22,12 @@
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import android.widget.TextView;
import com.android.systemui.R;
@@ -32,8 +35,11 @@
* Lightweight activity for editing text clipboard contents
*/
public class EditTextActivity extends Activity {
+ private static final String TAG = "EditTextActivity";
+
private EditText mEditText;
private ClipboardManager mClipboardManager;
+ private TextView mAttribution;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -42,6 +48,7 @@
findViewById(R.id.copy_button).setOnClickListener((v) -> saveToClipboard());
findViewById(R.id.share).setOnClickListener((v) -> share());
mEditText = findViewById(R.id.edit_text);
+ mAttribution = findViewById(R.id.attribution);
mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class));
}
@@ -53,7 +60,15 @@
finish();
return;
}
- // TODO: put clip attribution in R.id.attribution TextView
+ PackageManager pm = getApplicationContext().getPackageManager();
+ try {
+ CharSequence label = pm.getApplicationLabel(
+ pm.getApplicationInfo(mClipboardManager.getPrimaryClipSource(),
+ PackageManager.ApplicationInfoFlags.of(0)));
+ mAttribution.setText(getResources().getString(R.string.clipboard_edit_source, label));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + mClipboardManager.getPrimaryClipSource(), e);
+ }
mEditText.setText(clip.getItemAt(0).getText());
mEditText.requestFocus();
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index a29f3e9..f87fa96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.VibrationEffect
-import android.os.Vibrator
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
@@ -32,16 +31,14 @@
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -52,14 +49,11 @@
@Main private val uiExecutor: DelayableExecutor,
private val activityStarter: ActivityStarter,
private val keyguardStateController: KeyguardStateController,
- private val globalActionsComponent: GlobalActionsComponent,
private val taskViewFactory: Optional<TaskViewFactory>,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val lazyUiController: Lazy<ControlsUiController>,
- private val controlsMetricsLogger: ControlsMetricsLogger
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper
) : ControlActionCoordinator {
private var dialog: Dialog? = null
- private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
@@ -194,7 +188,7 @@
}
private fun vibrate(effect: VibrationEffect) {
- bgExecutor.execute { vibrator.vibrate(effect) }
+ vibrator.vibrate(effect)
}
private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index bce8784..1653e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -120,4 +121,11 @@
@IntoMap
@ClassKey(TvUnblockSensorActivity.class)
public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity);
+
+ /** Inject into HdmiCecSetMenuLanguageActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(HdmiCecSetMenuLanguageActivity.class)
+ public abstract Activity bindHdmiCecSetMenuLanguageActivity(
+ HdmiCecSetMenuLanguageActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index bbe9dbd..ec2beb1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -26,9 +26,15 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.clipboardoverlay.ClipboardListener;
import com.android.systemui.dreams.DreamOverlayRegistrant;
+import com.android.systemui.dreams.SmartSpaceComplication;
+import com.android.systemui.dreams.complication.DreamClockDateComplication;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication;
+import com.android.systemui.dreams.complication.DreamWeatherComplication;
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.dream.MediaDreamSentinel;
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -66,6 +72,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
@@ -211,4 +223,39 @@
@ClassKey(DreamOverlayRegistrant.class)
public abstract CoreStartable bindDreamOverlayRegistrant(
DreamOverlayRegistrant dreamOverlayRegistrant);
+
+ /** Inject into SmartSpaceComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(SmartSpaceComplication.Registrant.class)
+ public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
+ SmartSpaceComplication.Registrant registrant);
+
+ /** Inject into MediaDreamSentinel. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaDreamSentinel.class)
+ public abstract CoreStartable bindMediaDreamSentinel(
+ MediaDreamSentinel sentinel);
+
+ /** Inject into DreamClockTimeComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamClockTimeComplication.Registrant.class)
+ public abstract CoreStartable bindDreamClockTimeComplicationRegistrant(
+ DreamClockTimeComplication.Registrant registrant);
+
+ /** Inject into DreamClockDateComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamClockDateComplication.Registrant.class)
+ public abstract CoreStartable bindDreamClockDateComplicationRegistrant(
+ DreamClockDateComplication.Registrant registrant);
+
+ /** Inject into DreamWeatherComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamWeatherComplication.Registrant.class)
+ public abstract CoreStartable bindDreamWeatherComplicationRegistrant(
+ DreamWeatherComplication.Registrant registrant);
}
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/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
similarity index 78%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
index 861a4ed..c60cad8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.shared.mediattt;
+package com.android.systemui.decor
-parcelable DeviceInfo;
+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..77997e4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -29,9 +29,14 @@
import androidx.lifecycle.ViewModelStore;
import com.android.internal.policy.PhoneWindow;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.dream.DreamBackend;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationUtils;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import java.util.concurrent.Executor;
@@ -53,6 +58,8 @@
// 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;
+ private final DreamBackend mDreamBackend;
// A reference to the {@link Window} used to hold the dream overlay.
private Window mWindow;
@@ -68,19 +75,45 @@
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);
+ }
+ };
+
+ private DreamOverlayStateController mStateController;
+
@Inject
public DreamOverlayService(
Context context,
@Main Executor executor,
- DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
+ DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+ DreamOverlayStateController stateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mExecutor = executor;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+ mStateController = stateController;
final DreamOverlayComponent component =
dreamOverlayComponentFactory.create(mViewModelStore, mHost);
mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
setCurrentState(Lifecycle.State.CREATED);
mLifecycleRegistry = component.getLifecycleRegistry();
+ mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
+ mDreamBackend = component.getDreamBackend();
+ mDreamOverlayTouchMonitor.init();
}
private void setCurrentState(Lifecycle.State state) {
@@ -89,9 +122,11 @@
@Override
public void onDestroy() {
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
setCurrentState(Lifecycle.State.DESTROYED);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.removeView(mWindow.getDecorView());
+ mStateController.setOverlayActive(false);
super.onDestroy();
}
@@ -99,8 +134,12 @@
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
setCurrentState(Lifecycle.State.STARTED);
mExecutor.execute(() -> {
+ mStateController.setAvailableComplicationTypes(
+ ComplicationUtils.convertComplicationTypes(
+ mDreamBackend.getEnabledComplications()));
addOverlayWindowLocked(layoutParams);
setCurrentState(Lifecycle.State.RESUMED);
+ mStateController.setOverlayActive(true);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e838848..bc5a52a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
package com.android.systemui.dreams;
+import android.util.Log;
+
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,8 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -41,6 +45,16 @@
@SysUISingleton
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
+ private static final String TAG = "DreamOverlayStateCtlr";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+
+ private static final int OP_CLEAR_STATE = 1;
+ private static final int OP_SET_STATE = 2;
+
+ private int mState;
+
/**
* Callback for dream overlay events.
*/
@@ -50,11 +64,26 @@
*/
default void onComplicationsChanged() {
}
+
+ /**
+ * Called when the dream overlay state changes.
+ */
+ default void onStateChanged() {
+ }
+
+ /**
+ * Called when the available complication types changes.
+ */
+ default void onAvailableComplicationTypesChanged() {
+ }
}
private final Executor mExecutor;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ @Complication.ComplicationType
+ private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
+
private final Collection<Complication> mComplications = new HashSet();
@VisibleForTesting
@@ -89,7 +118,31 @@
* Returns collection of present {@link Complication}.
*/
public Collection<Complication> getComplications() {
- return Collections.unmodifiableCollection(mComplications);
+ return getComplications(true);
+ }
+
+ /**
+ * Returns collection of present {@link Complication}.
+ */
+ public Collection<Complication> getComplications(boolean filterByAvailability) {
+ return Collections.unmodifiableCollection(filterByAvailability
+ ? mComplications
+ .stream()
+ .filter(complication -> {
+ @Complication.ComplicationType
+ final int requiredTypes = complication.getRequiredTypeAvailability();
+ return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
+ })
+ .collect(Collectors.toCollection(HashSet::new))
+ : mComplications);
+ }
+
+ private void notifyCallbacks(Consumer<Callback> callbackConsumer) {
+ mExecutor.execute(() -> {
+ for (Callback callback : mCallbacks) {
+ callbackConsumer.accept(callback);
+ }
+ });
}
@Override
@@ -117,4 +170,58 @@
mCallbacks.remove(callback);
});
}
+
+ /**
+ * Returns whether the overlay is active.
+ * @return {@code true} if overlay is active, {@code false} otherwise.
+ */
+ public boolean isOverlayActive() {
+ return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+ }
+
+ private boolean containsState(int state) {
+ return (mState & state) != 0;
+ }
+
+ private void modifyState(int op, int state) {
+ final int existingState = mState;
+ switch (op) {
+ case OP_CLEAR_STATE:
+ mState &= ~state;
+ break;
+ case OP_SET_STATE:
+ mState |= state;
+ break;
+ }
+
+ if (existingState != mState) {
+ notifyCallbacks(callback -> callback.onStateChanged());
+ }
+ }
+
+ /**
+ * Sets whether the overlay is active.
+ * @param active {@code true} if overlay is active, {@code false} otherwise.
+ */
+ public void setOverlayActive(boolean active) {
+ modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
+ }
+
+ /**
+ * Returns the available complication types.
+ */
+ @Complication.ComplicationType
+ public int getAvailableComplicationTypes() {
+ return mAvailableComplicationTypes;
+ }
+
+ /**
+ * Sets the available complication types for the dream overlay.
+ */
+ public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
+ mExecutor.execute(() -> {
+ mAvailableComplicationTypes = types;
+ mCallbacks.forEach(callback -> callback.onAvailableComplicationTypesChanged());
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
new file mode 100644
index 0000000..09221b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import javax.inject.Inject;
+
+/**
+ * {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
+ * {@link Complication}
+ */
+public class SmartSpaceComplication implements Complication {
+ /**
+ * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final SmartSpaceComplication mComplication;
+
+ /**
+ * Default constructor for {@link SmartSpaceComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ SmartSpaceComplication smartSpaceComplication,
+ LockscreenSmartspaceController smartSpaceController) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = smartSpaceComplication;
+ mSmartSpaceController = smartSpaceController;
+ }
+
+ @Override
+ public void start() {
+ if (mSmartSpaceController.isEnabled()) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final Context mContext;
+
+ protected SmartSpaceComplicationViewHolder(
+ Context context,
+ LockscreenSmartspaceController smartSpaceController) {
+ mSmartSpaceController = smartSpaceController;
+ mContext = context;
+ }
+
+ @Override
+ public View getView() {
+ final FrameLayout smartSpaceContainer = new FrameLayout(mContext);
+ smartSpaceContainer.addView(
+ mSmartSpaceController.buildAndConnectView(smartSpaceContainer),
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ return smartSpaceContainer;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0, true);
+ }
+ }
+
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final Context mContext;
+
+ @Inject
+ public SmartSpaceComplication(Context context,
+ LockscreenSmartspaceController smartSpaceController) {
+ mContext = context;
+ mSmartSpaceController = smartSpaceController;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 96cf50d..fe458f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -154,6 +154,27 @@
int CATEGORY_SYSTEM = 1 << 1;
/**
+ * The type of dream complications which can be provided by a {@link Complication}.
+ */
+ @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
+ COMPLICATION_TYPE_NONE,
+ COMPLICATION_TYPE_TIME,
+ COMPLICATION_TYPE_DATE,
+ COMPLICATION_TYPE_WEATHER,
+ COMPLICATION_TYPE_AIR_QUALITY,
+ COMPLICATION_TYPE_CAST_INFO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ComplicationType {}
+
+ int COMPLICATION_TYPE_NONE = 0;
+ int COMPLICATION_TYPE_TIME = 1;
+ int COMPLICATION_TYPE_DATE = 1 << 1;
+ int COMPLICATION_TYPE_WEATHER = 1 << 2;
+ int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
+ int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+
+ /**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
* parent entity for information and actions.
*/
@@ -207,4 +228,15 @@
* @return a {@link ViewHolder} for this {@link Complication} instance.
*/
ViewHolder createView(ComplicationViewModel model);
+
+ /**
+ * Returns the types that must be present in order for this complication to participate on
+ * the dream overlay. By default, this method returns
+ * {@code Complication.COMPLICATION_TYPE_NONE} to indicate no types are required.
+ * @return
+ */
+ @Complication.ComplicationType
+ default int getRequiredTypeAvailability() {
+ return Complication.COMPLICATION_TYPE_NONE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
index 76818fa..f6fe8d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -42,6 +42,10 @@
setValue(mDreamOverlayStateController.getComplications());
}
+ @Override
+ public void onAvailableComplicationTypesChanged() {
+ setValue(mDreamOverlayStateController.getComplications());
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index cb24ae6..5223f37 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -25,10 +25,13 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
+import com.android.systemui.R;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
@@ -102,6 +105,8 @@
final int direction = getLayoutParams().getDirection();
+ final boolean snapsToGuide = getLayoutParams().snapsToGuide();
+
// If no parent, view is the anchor. In this case, it is given the highest priority for
// alignment. All alignment preferences are done in relation to the parent container.
final boolean isRoot = head == mView;
@@ -125,6 +130,11 @@
} else {
params.startToEnd = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_DOWN
+ || direction == ComplicationLayoutParams.DIRECTION_UP)) {
+ params.endToStart = R.id.complication_start_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_TOP:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
@@ -132,6 +142,11 @@
} else {
params.topToBottom = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_END
+ || direction == ComplicationLayoutParams.DIRECTION_START)) {
+ params.endToStart = R.id.complication_top_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_BOTTOM:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
@@ -139,6 +154,11 @@
} else {
params.bottomToTop = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_END
+ || direction == ComplicationLayoutParams.DIRECTION_START)) {
+ params.topToBottom = R.id.complication_bottom_guide;
+ }
break;
case ComplicationLayoutParams.POSITION_END:
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
@@ -146,6 +166,11 @@
} else {
params.endToStart = head.getId();
}
+ if (snapsToGuide
+ && (direction == ComplicationLayoutParams.DIRECTION_UP
+ || direction == ComplicationLayoutParams.DIRECTION_DOWN)) {
+ params.startToEnd = R.id.complication_end_guide;
+ }
break;
}
});
@@ -153,6 +178,16 @@
mView.setLayoutParams(params);
}
+ private void setGuide(ConstraintLayout.LayoutParams lp, int validDirections,
+ Consumer<ConstraintLayout.LayoutParams> consumer) {
+ final ComplicationLayoutParams layoutParams = getLayoutParams();
+ if (!layoutParams.snapsToGuide()) {
+ return;
+ }
+
+ consumer.accept(lp);
+ }
+
/**
* Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
* being shown further.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index f9a69fa..8e8cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -40,13 +40,13 @@
@interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
- static final int POSITION_TOP = 1 << 0;
+ public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
- static final int POSITION_BOTTOM = 1 << 1;
+ public static final int POSITION_BOTTOM = 1 << 1;
/** Align view with the start of parent or end of preceding {@link Complication}. */
- static final int POSITION_START = 1 << 2;
+ public static final int POSITION_START = 1 << 2;
/** Align view with the end of parent or start of preceding {@link Complication}. */
- static final int POSITION_END = 1 << 3;
+ public static final int POSITION_END = 1 << 3;
private static final int FIRST_POSITION = POSITION_TOP;
private static final int LAST_POSITION = POSITION_END;
@@ -61,13 +61,13 @@
@interface Direction {}
/** Position view upward from position. */
- static final int DIRECTION_UP = 1 << 0;
+ public static final int DIRECTION_UP = 1 << 0;
/** Position view downward from position. */
- static final int DIRECTION_DOWN = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 1;
/** Position view towards the start of the parent. */
- static final int DIRECTION_START = 1 << 2;
+ public static final int DIRECTION_START = 1 << 2;
/** Position view towards the end of parent. */
- static final int DIRECTION_END = 1 << 3;
+ public static final int DIRECTION_END = 1 << 3;
@Position
private final int mPosition;
@@ -77,6 +77,8 @@
private final int mWeight;
+ private final boolean mSnapToGuide;
+
// Do not allow specifying opposite positions
private static final int[] INVALID_POSITIONS =
{ POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
@@ -104,6 +106,27 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
+ this(width, height, position, direction, weight, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
+ * will be automatically set to align with a predetermined guide for that
+ * side. For example, if the complication is aligned to the top end and
+ * direction is down, then the width of the complication will be set to span
+ * from the end of the parent to the guide.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -118,6 +141,8 @@
mDirection = direction;
mWeight = weight;
+
+ mSnapToGuide = snapToGuide;
}
/**
@@ -128,6 +153,7 @@
mPosition = source.mPosition;
mDirection = source.mDirection;
mWeight = source.mWeight;
+ mSnapToGuide = source.mSnapToGuide;
}
private static boolean validateDirection(@Position int position, @Direction int direction) {
@@ -180,7 +206,19 @@
return mPosition;
}
+ /**
+ * Returns the set weight for the complication. The weight determines ordering a complication
+ * given the same position/direction.
+ */
public int getWeight() {
return mWeight;
}
+
+ /**
+ * Returns whether the complication's dimension perpendicular to direction should be
+ * automatically set.
+ */
+ public boolean snapsToGuide() {
+ return mSnapToGuide;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
new file mode 100644
index 0000000..a4a0075
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+
+import com.android.settingslib.dream.DreamBackend;
+
+import java.util.Set;
+
+/**
+ * A collection of utility methods for working with {@link Complication}.
+ */
+public class ComplicationUtils {
+ /**
+ * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+ * {@link ComplicationType}.
+ */
+ @Complication.ComplicationType
+ public static int convertComplicationType(@DreamBackend.ComplicationType int type) {
+ switch (type) {
+ case DreamBackend.COMPLICATION_TYPE_TIME:
+ return COMPLICATION_TYPE_TIME;
+ case DreamBackend.COMPLICATION_TYPE_DATE:
+ return COMPLICATION_TYPE_DATE;
+ case DreamBackend.COMPLICATION_TYPE_WEATHER:
+ return COMPLICATION_TYPE_WEATHER;
+ case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
+ return COMPLICATION_TYPE_AIR_QUALITY;
+ case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
+ return COMPLICATION_TYPE_CAST_INFO;
+ default:
+ return COMPLICATION_TYPE_NONE;
+ }
+ }
+
+ /**
+ * Converts a set of {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
+ * a combined complications types state.
+ */
+ @Complication.ComplicationType
+ public static int convertComplicationTypes(@DreamBackend.ComplicationType Set<Integer> types) {
+ return types.stream().mapToInt(ComplicationUtils::convertComplicationType).reduce(
+ COMPLICATION_TYPE_NONE, (a, b) -> a | b);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
new file mode 100644
index 0000000..6861c74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Date Complication that produce Clock Date view holder.
+ */
+public class DreamClockDateComplication implements Complication {
+ DreamClockDateComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamClockDateComplication}.
+ */
+ @Inject
+ public DreamClockDateComplication(
+ DreamClockDateComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_DATE;
+ }
+
+ /**
+ * Create {@link DreamClockDateViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} responsbile for registering {@link DreamClockDateComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamClockDateComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamClockDateComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamClockDateComplication dreamClockDateComplication) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamClockDateComplication;
+ }
+
+ @Override
+ public void start() {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Clock Date Complication View.
+ */
+ public static class DreamClockDateViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
new file mode 100644
index 0000000..936767a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Clock Time Complication that produce Clock Time view holder.
+ */
+public class DreamClockTimeComplication implements Complication {
+ DreamClockTimeComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamClockTimeComplication}.
+ */
+ @Inject
+ public DreamClockTimeComplication(
+ DreamClockTimeComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_TIME;
+ }
+
+ /**
+ * Create {@link DreamClockTimeViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} responsbile for registering {@link DreamClockTimeComplication} with
+ * SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamClockTimeComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamClockTimeComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamClockTimeComplication dreamClockTimeComplication) {
+ super(context);
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamClockTimeComplication;
+ }
+
+ @Override
+ public void start() {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Clock Time Complication View.
+ */
+ public static class DreamClockTimeViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
new file mode 100644
index 0000000..f5c5a43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
+
+import android.app.smartspace.SmartspaceAction;
+import android.app.smartspace.SmartspaceTarget;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Weather Complication that produce Weather view holder.
+ */
+public class DreamWeatherComplication implements Complication {
+ DreamWeatherComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link DreamWeatherComplication}.
+ */
+ @Inject
+ public DreamWeatherComplication(
+ DreamWeatherComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_WEATHER;
+ }
+
+ /**
+ * Create {@link DreamWeatherViewHolder}.
+ */
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ /**
+ * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
+ */
+ public static class Registrant extends CoreStartable {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final DreamWeatherComplication mComplication;
+
+ /**
+ * Default constructor to register {@link DreamWeatherComplication}.
+ */
+ @Inject
+ public Registrant(Context context,
+ LockscreenSmartspaceController smartspaceController,
+ DreamOverlayStateController dreamOverlayStateController,
+ DreamWeatherComplication dreamWeatherComplication) {
+ super(context);
+ mSmartSpaceController = smartspaceController;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = dreamWeatherComplication;
+ }
+
+ @Override
+ public void start() {
+ if (mSmartSpaceController.isEnabled()) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ /**
+ * ViewHolder to contain value/logic associated with a Weather Complication View.
+ */
+ public static class DreamWeatherViewHolder implements ViewHolder {
+ private final TextView mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final DreamWeatherViewController mViewController;
+
+ @Inject
+ DreamWeatherViewHolder(
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+ DreamWeatherViewController controller,
+ @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+ ComplicationLayoutParams layoutParams) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ mViewController = controller;
+ mViewController.init();
+ }
+
+ @Override
+ public TextView getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+
+ /**
+ * ViewController to contain value/logic associated with a Weather Complication View.
+ */
+ static class DreamWeatherViewController extends ViewController<TextView> {
+ private final LockscreenSmartspaceController mSmartSpaceController;
+ private SmartspaceTargetListener mSmartspaceTargetListener;
+
+ @Inject
+ DreamWeatherViewController(
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
+ LockscreenSmartspaceController smartspaceController
+ ) {
+ super(view);
+ mSmartSpaceController = smartspaceController;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mSmartspaceTargetListener = targets -> targets.forEach(
+ t -> {
+ if (t instanceof SmartspaceTarget
+ && ((SmartspaceTarget) t).getFeatureType()
+ == SmartspaceTarget.FEATURE_WEATHER) {
+ final SmartspaceTarget target = (SmartspaceTarget) t;
+ final SmartspaceAction headerAction = target.getHeaderAction();
+ if (headerAction == null || TextUtils.isEmpty(
+ headerAction.getTitle())) {
+ return;
+ }
+
+ String temperature = headerAction.getTitle().toString();
+ mView.setText(temperature);
+ final Icon icon = headerAction.getIcon();
+ if (icon != null) {
+ final int iconSize =
+ getResources().getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_size);
+ final Drawable iconDrawable = icon.loadDrawable(getContext());
+ iconDrawable.setBounds(0, 0, iconSize, iconSize);
+ mView.setCompoundDrawables(iconDrawable, null, null, null);
+ mView.setCompoundDrawablePadding(
+ getResources().getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_padding));
+
+ }
+ }
+ });
+ mSmartSpaceController.addListener(mSmartspaceTargetListener);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mSmartSpaceController.removeListener(mSmartspaceTargetListener);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
new file mode 100644
index 0000000..eaffb1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockDateComplication.DreamClockDateViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockDateComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamClockDateComplicationComponent.DreamClockDateComplicationModule.class,
+})
+@DreamClockDateComplicationComponent.DreamClockDateComplicationScope
+public interface DreamClockDateComplicationComponent {
+ /**
+ * Creates {@link DreamClockDateViewHolder}.
+ */
+ DreamClockDateViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamClockDateComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamClockDateComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamClockDateComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamClockDateComplicationComponent}.
+ */
+ @Module
+ interface DreamClockDateComplicationModule {
+ String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
+ String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
+ "clock_date_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 2;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamClockDateComplicationScope
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
+ static View provideComplicationView(LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull(
+ layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
+ null, false),
+ "R.layout.dream_overlay_complication_clock_date did not properly inflated");
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamClockDateComplicationScope
+ @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java
new file mode 100644
index 0000000..d539f5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.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.systemui.dreams.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextClock;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamClockTimeComplication.DreamClockTimeViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamClockTimeComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Clock Time {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.class,
+})
+@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope
+public interface DreamClockTimeComplicationComponent {
+ /**
+ * Creates {@link DreamClockTimeViewHolder}.
+ */
+ DreamClockTimeViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamClockTimeComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamClockTimeComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamClockTimeComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamClockTimeComplicationComponent}.
+ */
+ @Module
+ interface DreamClockTimeComplicationModule {
+ String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
+ String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
+ "clock_time_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 0;
+ String TAG_WEIGHT = "'wght' ";
+ int WEIGHT = 200;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamClockTimeComplicationScope
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
+ static View provideComplicationView(LayoutInflater layoutInflater) {
+ final TextClock view = Preconditions.checkNotNull((TextClock)
+ layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
+ null, false),
+ "R.layout.dream_overlay_complication_clock_time did not properly inflated");
+ view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
+ return view;
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamClockTimeComplicationScope
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_UP,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
new file mode 100644
index 0000000..a282594
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
+ * the
+ * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+ DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
+})
+@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
+public interface DreamWeatherComplicationComponent {
+ /**
+ * Creates {@link DreamWeatherViewHolder}.
+ */
+ DreamWeatherViewHolder getViewHolder();
+
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamWeatherComplicationScope {
+ }
+
+ /**
+ * Generates {@link DreamWeatherComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamWeatherComplicationComponent create();
+ }
+
+ /**
+ * Scoped values for {@link DreamWeatherComplicationComponent}.
+ */
+ @Module
+ interface DreamWeatherComplicationModule {
+ String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
+ String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
+ "weather_complication_layout_params";
+ // Order weight of insert into parent container
+ int INSERT_ORDER_WEIGHT = 1;
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @DreamWeatherComplicationScope
+ @Named(DREAM_WEATHER_COMPLICATION_VIEW)
+ static TextView provideComplicationView(LayoutInflater layoutInflater) {
+ return Preconditions.checkNotNull((TextView)
+ layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
+ null, false),
+ "R.layout.dream_overlay_complication_weather did not properly inflated");
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @DreamWeatherComplicationScope
+ @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ INSERT_ORDER_WEIGHT,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
new file mode 100644
index 0000000..8e4fb37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+import com.android.systemui.dagger.SystemUIBinder;
+
+import dagger.Module;
+
+/**
+ * Module for all components with corresponding dream layer complications registered in
+ * {@link SystemUIBinder}.
+ */
+@Module(subcomponents = {
+ DreamClockTimeComplicationComponent.class,
+ DreamClockDateComplicationComponent.class,
+ DreamWeatherComplicationComponent.class,
+})
+public interface RegisteredComplicationsModule {
+}
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..d8af9e5 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,20 @@
package com.android.systemui.dreams.dagger;
+import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
+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,
+ RegisteredComplicationsModule.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..60278a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -22,9 +22,11 @@
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.ViewModelStore;
+import com.android.settingslib.dream.DreamBackend;
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 +66,10 @@
/** Builds a {@link androidx.lifecycle.LifecycleOwner} */
LifecycleOwner getLifecycleOwner();
+
+ /** Builds a {@link DreamOverlayTouchMonitor} */
+ DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
+
+ /** Builds a ${@link DreamBackend} */
+ DreamBackend getDreamBackend();
}
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..efa063f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -17,15 +17,18 @@
package com.android.systemui.dreams.dagger;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.util.Preconditions;
+import com.android.settingslib.dream.DreamBackend;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.battery.BatteryMeterViewController;
@@ -140,4 +143,16 @@
static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
return new LifecycleRegistry(lifecycleOwner);
}
+
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
+ return lifecycleOwner.getLifecycle();
+ }
+
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ static DreamBackend providesDreamBackend(Context context) {
+ return DreamBackend.getInstance(context);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
new file mode 100644
index 0000000..d16c8c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
+ */
+public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+ /**
+ * An interface for creating ValueAnimators.
+ */
+ public interface ValueAnimatorCreator {
+ /**
+ * Creates {@link ValueAnimator}.
+ */
+ ValueAnimator create(float start, float finish);
+ }
+
+ /**
+ * An interface for obtaining VelocityTrackers.
+ */
+ public interface VelocityTrackerFactory {
+ /**
+ * Obtains {@link VelocityTracker}.
+ */
+ VelocityTracker obtain();
+ }
+
+ public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
+
+ private static final String TAG = "BouncerSwipeTouchHandler";
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final float mBouncerZoneScreenPercentage;
+
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private float mCurrentExpansion;
+ private final StatusBar mStatusBar;
+
+ private VelocityTracker mVelocityTracker;
+
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ private Boolean mCapture;
+
+ private TouchSession mTouchSession;
+
+ private ValueAnimatorCreator mValueAnimatorCreator;
+
+ private VelocityTrackerFactory mVelocityTrackerFactory;
+
+ private final GestureDetector.OnGestureListener mOnGestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ boolean mTrack;
+ boolean mBouncerPresent;
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ // We only consider gestures that originate from the lower portion of the
+ // screen.
+ final float displayHeight = mStatusBar.getDisplayHeight();
+
+ mBouncerPresent = mStatusBar.isBouncerShowing();
+
+ // The target zone is either at the top or bottom of the screen, dependent on
+ // whether the bouncer is present.
+ final float zonePercentage =
+ Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
+ / displayHeight;
+
+ mTrack = zonePercentage < mBouncerZoneScreenPercentage;
+
+ // Never capture onDown. While this might lead to some false positive touches
+ // being sent to other windows/layers, this is necessary to make sure the
+ // proper touch event sequence is received by others in the event we do not
+ // consume the sequence here.
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ // Do not handle scroll gestures if not tracking touch events.
+ if (!mTrack) {
+ return false;
+ }
+
+ if (mCapture == null) {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ mCapture = Math.abs(distanceY) > Math.abs(distanceX);
+
+ if (mCapture) {
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ mStatusBarKeyguardViewManager.showBouncer(false);
+ }
+ }
+
+ if (!mCapture) {
+ return false;
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+ // is fully hidden at full expansion (1) and fully visible when fully collapsed
+ // (0).
+ final float screenTravelPercentage =
+ Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight());
+ setPanelExpansion(
+ mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);
+
+ return true;
+ }
+ };
+
+ private void setPanelExpansion(float expansion) {
+ mCurrentExpansion = expansion;
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
+ }
+
+ @Inject
+ public BouncerSwipeTouchHandler(
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBar statusBar,
+ NotificationShadeWindowController notificationShadeWindowController,
+ ValueAnimatorCreator valueAnimatorCreator,
+ VelocityTrackerFactory velocityTrackerFactory,
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ FlingAnimationUtils flingAnimationUtils,
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ FlingAnimationUtils flingAnimationUtilsClosing,
+ @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
+ mStatusBar = statusBar;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mBouncerZoneScreenPercentage = swipeRegionPercentage;
+ mFlingAnimationUtils = flingAnimationUtils;
+ mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
+ mValueAnimatorCreator = valueAnimatorCreator;
+ mVelocityTrackerFactory = velocityTrackerFactory;
+ }
+
+ @Override
+ public void onSessionStart(TouchSession session) {
+ mVelocityTracker = mVelocityTrackerFactory.obtain();
+ mTouchSession = session;
+ mVelocityTracker.clear();
+ mNotificationShadeWindowController.setForcePluginOpen(true, this);
+ session.registerGestureListener(mOnGestureListener);
+ session.registerInputListener(ev -> onMotionEvent(ev));
+
+ }
+
+ @Override
+ public void onSessionEnd(TouchSession session) {
+ mVelocityTracker.recycle();
+ mCapture = null;
+ mNotificationShadeWindowController.setForcePluginOpen(false, this);
+ }
+
+ private void onMotionEvent(InputEvent event) {
+ if (!(event instanceof MotionEvent)) {
+ Log.e(TAG, "non MotionEvent received:" + event);
+ return;
+ }
+
+ final MotionEvent motionEvent = (MotionEvent) event;
+
+ switch(motionEvent.getAction()) {
+ case MotionEvent.ACTION_UP:
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (mCapture == null || !mCapture) {
+ break;
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ mVelocityTracker.computeCurrentVelocity(1000);
+ final float verticalVelocity = mVelocityTracker.getYVelocity();
+ final float horizontalVelocity = mVelocityTracker.getXVelocity();
+
+ final float velocityVector =
+ (float) Math.hypot(horizontalVelocity, verticalVelocity);
+
+
+ final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
+ ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE;
+ flingToExpansion(verticalVelocity, expansion);
+
+ if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+ mStatusBarKeyguardViewManager.reset(false);
+ }
+ mTouchSession.pop();
+ break;
+ default:
+ mVelocityTracker.addMovement(motionEvent);
+ break;
+ }
+ }
+
+ private ValueAnimator createExpansionAnimator(float targetExpansion) {
+ final ValueAnimator animator =
+ mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
+ animator.addUpdateListener(
+ animation -> {
+ setPanelExpansion((float) animation.getAnimatedValue());
+ });
+ return animator;
+ }
+
+ protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
+ // Fully expand if the user has expanded the bouncer less than halfway or final velocity was
+ // positive, indicating an downward direction.
+ if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
+ } else {
+ return velocity > 0;
+ }
+ }
+
+ protected void flingToExpansion(float velocity, float expansion) {
+ final float viewHeight = mStatusBar.getDisplayHeight();
+ final float currentHeight = viewHeight * mCurrentExpansion;
+ final float targetHeight = viewHeight * expansion;
+
+ final ValueAnimator animator = createExpansionAnimator(expansion);
+ if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
+ // The animation utils deal in pixel units, rather than expansion height.
+ mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight);
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight);
+ }
+
+ animator.start();
+ }
+}
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/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
new file mode 100644
index 0000000..b9436f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.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 com.android.systemui.dreams.touch.dagger;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.VelocityTracker;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+/**
+ * This module captures the components associated with {@link BouncerSwipeTouchHandler}.
+ */
+@Module
+public class BouncerSwipeModule {
+ /**
+ * The region, defined as the percentage of the screen, from which a touch gesture to start
+ * swiping up to the bouncer can occur.
+ */
+ public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+
+ /**
+ * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
+ */
+ public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING =
+ "swipe_to_bouncer_fling_animation_utils_closing";
+
+ /**
+ * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening.
+ */
+ public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING =
+ "swipe_to_bouncer_fling_animation_utils_opening";
+
+ /**
+ * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream.
+ */
+ @Provides
+ @IntoSet
+ public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+ BouncerSwipeTouchHandler touchHandler) {
+ return touchHandler;
+ }
+
+ /**
+ * Provides {@link android.view.animation.AnimationUtils} for closing.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing(
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+ return flingAnimationUtilsBuilderProvider.get()
+ .reset()
+ .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .build();
+ }
+
+ /**
+ * Provides {@link android.view.animation.AnimationUtils} for opening.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening(
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
+ return flingAnimationUtilsBuilderProvider.get()
+ .reset()
+ .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .build();
+ }
+
+ /**
+ * Provides the region to start swipe gestures from.
+ */
+ @Provides
+ @Named(SWIPE_TO_BOUNCER_START_REGION)
+ public static float providesSwipeToBouncerStartRegion(@Main Resources resources) {
+ TypedValue typedValue = new TypedValue();
+ resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage,
+ typedValue, true);
+ return typedValue.getFloat();
+ }
+
+ /**
+ * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
+ * a wrapper around {@link ValueAnimator}.
+ */
+ @Provides
+ public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() {
+ return (start, finish) -> ValueAnimator.ofFloat(start, finish);
+ }
+
+ /**
+ * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a
+ * passthrough to {@link android.view.VelocityTracker}.
+ */
+ @Provides
+ public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() {
+ return () -> VelocityTracker.obtain();
+ }
+}
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..dad0004
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch.dagger;
+
+import dagger.Module;
+
+/**
+ * {@link DreamTouchModule} encapsulates dream touch-related components.
+ */
+@Module(includes = {
+ BouncerSwipeModule.class,
+ }, 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/fgsmanager/FgsManagerDialog.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt
deleted file mode 100644
index 42f3512..0000000
--- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.fgsmanager
-
-import android.content.Context
-import android.os.Bundle
-import android.text.format.DateUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.annotation.GuardedBy
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.R
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.fgsmanager.FgsManagerDialogController.RunningApp
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.time.SystemClock
-import java.util.concurrent.Executor
-
-/**
- * Dialog which shows a list of running foreground services and offers controls to them
- */
-class FgsManagerDialog(
- context: Context,
- private val executor: Executor,
- @Background private val backgroundExecutor: Executor,
- private val systemClock: SystemClock,
- private val fgsManagerDialogController: FgsManagerDialogController
-) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog) {
-
- private val appListRecyclerView: RecyclerView = RecyclerView(this.context)
- private val adapter: AppListAdapter = AppListAdapter()
-
- init {
- setTitle(R.string.fgs_manager_dialog_title)
- setView(appListRecyclerView)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- appListRecyclerView.layoutManager = LinearLayoutManager(context)
- fgsManagerDialogController.registerDialogForChanges(
- object : FgsManagerDialogController.FgsManagerDialogCallback {
- override fun onRunningAppsChanged(apps: List<RunningApp>) {
- executor.execute {
- adapter.setData(apps)
- }
- }
- }
- )
- appListRecyclerView.adapter = adapter
- backgroundExecutor.execute { adapter.setData(fgsManagerDialogController.runningAppList) }
- }
-
- private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
- private val lock = Any()
-
- @GuardedBy("lock")
- private val data: MutableList<RunningApp> = ArrayList()
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
- return AppItemViewHolder(LayoutInflater.from(context)
- .inflate(R.layout.fgs_manager_app_item, parent, false))
- }
-
- override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
- var runningApp: RunningApp
- synchronized(lock) {
- runningApp = data[position]
- }
- with(holder) {
- iconView.setImageDrawable(runningApp.mIcon)
- appLabelView.text = runningApp.mAppLabel
- durationView.text = DateUtils.formatDuration(
- Math.max(systemClock.elapsedRealtime() - runningApp.mTimeStarted, 60000),
- DateUtils.LENGTH_MEDIUM)
- stopButton.setOnClickListener {
- fgsManagerDialogController
- .stopAllFgs(runningApp.mUserId, runningApp.mPackageName)
- }
- }
- }
-
- override fun getItemCount(): Int {
- synchronized(lock) { return data.size }
- }
-
- fun setData(newData: List<RunningApp>) {
- var oldData: List<RunningApp>
- synchronized(lock) {
- oldData = ArrayList(data)
- data.clear()
- data.addAll(newData)
- }
-
- DiffUtil.calculateDiff(object : DiffUtil.Callback() {
- override fun getOldListSize(): Int {
- return oldData.size
- }
-
- override fun getNewListSize(): Int {
- return newData.size
- }
-
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int):
- Boolean {
- return oldData[oldItemPosition] == newData[newItemPosition]
- }
-
- override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int):
- Boolean {
- return true // TODO, look into updating the time subtext
- }
- }).dispatchUpdatesTo(this)
- }
- }
-
- private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
- val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label)
- val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration)
- val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon)
- val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt
deleted file mode 100644
index 159ed39..0000000
--- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.fgsmanager
-
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.NameNotFoundException
-import android.graphics.drawable.Drawable
-import android.os.Handler
-import android.os.UserHandle
-import android.util.ArrayMap
-import android.util.Log
-import androidx.annotation.GuardedBy
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.policy.RunningFgsController
-import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime
-import javax.inject.Inject
-
-/**
- * Controls events relevant to FgsManagerDialog
- */
-class FgsManagerDialogController @Inject constructor(
- private val packageManager: PackageManager,
- @Background private val backgroundHandler: Handler,
- private val runningFgsController: RunningFgsController
-) : RunningFgsController.Callback {
- private val lock = Any()
- private val clearCacheToken = Any()
-
- @GuardedBy("lock")
- private var runningApps: Map<UserPackageTime, RunningApp>? = null
- @GuardedBy("lock")
- private var listener: FgsManagerDialogCallback? = null
-
- interface FgsManagerDialogCallback {
- fun onRunningAppsChanged(apps: List<RunningApp>)
- }
-
- data class RunningApp(
- val mUserId: Int,
- val mPackageName: String,
- val mAppLabel: CharSequence,
- val mIcon: Drawable,
- val mTimeStarted: Long
- )
-
- val runningAppList: List<RunningApp>
- get() {
- synchronized(lock) {
- if (runningApps == null) {
- onFgsPackagesChangedLocked(runningFgsController.getPackagesWithFgs())
- }
- return convertToRunningAppList(runningApps!!)
- }
- }
-
- fun registerDialogForChanges(callback: FgsManagerDialogCallback) {
- synchronized(lock) {
- runningFgsController.addCallback(this)
- listener = callback
- backgroundHandler.removeCallbacksAndMessages(clearCacheToken)
- }
- }
-
- fun onFinishDialog() {
- synchronized(lock) {
- listener = null
- // Keep data such as icons cached for some time since loading can be slow
- backgroundHandler.postDelayed(
- {
- synchronized(lock) {
- runningFgsController.removeCallback(this)
- runningApps = null
- }
- }, clearCacheToken, RUNNING_APP_CACHE_TIMEOUT_MILLIS)
- }
- }
-
- private fun onRunningAppsChanged(apps: ArrayMap<UserPackageTime, RunningApp>) {
- listener?.let {
- backgroundHandler.post { it.onRunningAppsChanged(convertToRunningAppList(apps)) }
- }
- }
-
- override fun onFgsPackagesChanged(packages: List<UserPackageTime>) {
- backgroundHandler.post {
- synchronized(lock) { onFgsPackagesChangedLocked(packages) }
- }
- }
-
- /**
- * Run on background thread
- */
- private fun onFgsPackagesChangedLocked(packages: List<UserPackageTime>) {
- val newRunningApps = ArrayMap<UserPackageTime, RunningApp>()
- for (packageWithFgs in packages) {
- val ra = runningApps?.get(packageWithFgs)
- if (ra == null) {
- val userId = packageWithFgs.userId
- val packageName = packageWithFgs.packageName
- try {
- val ai = packageManager.getApplicationInfo(packageName, 0)
- var icon = packageManager.getApplicationIcon(ai)
- icon = packageManager.getUserBadgedIcon(icon,
- UserHandle.of(userId))
- val label = packageManager.getApplicationLabel(ai)
- newRunningApps[packageWithFgs] = RunningApp(userId, packageName,
- label, icon, packageWithFgs.startTimeMillis)
- } catch (e: NameNotFoundException) {
- Log.e(LOG_TAG,
- "Application info not found: $packageName", e)
- }
- } else {
- newRunningApps[packageWithFgs] = ra
- }
- }
- runningApps = newRunningApps
- onRunningAppsChanged(newRunningApps)
- }
-
- fun stopAllFgs(userId: Int, packageName: String) {
- runningFgsController.stopFgs(userId, packageName)
- }
-
- companion object {
- private val LOG_TAG = FgsManagerDialogController::class.java.simpleName
- private const val RUNNING_APP_CACHE_TIMEOUT_MILLIS: Long = 20_000
-
- private fun convertToRunningAppList(apps: Map<UserPackageTime, RunningApp>):
- List<RunningApp> {
- val result = mutableListOf<RunningApp>()
- result.addAll(apps.values)
- result.sortWith { a: RunningApp, b: RunningApp ->
- b.mTimeStarted.compareTo(a.mTimeStarted)
- }
- return result
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt
deleted file mode 100644
index 2874929..0000000
--- a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.fgsmanager
-
-import android.content.Context
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.animation.DialogLaunchAnimator
-import android.content.DialogInterface
-import android.view.View
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.util.time.SystemClock
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Factory to create [FgsManagerDialog] instances
- */
-@SysUISingleton
-class FgsManagerDialogFactory
-@Inject constructor(
- private val context: Context,
- @Main private val executor: Executor,
- @Background private val backgroundExecutor: Executor,
- private val systemClock: SystemClock,
- private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val fgsManagerDialogController: FgsManagerDialogController
-) {
-
- val lock = Any()
-
- companion object {
- private var fgsManagerDialog: FgsManagerDialog? = null
- }
-
- /**
- * Creates the dialog if it doesn't exist
- */
- fun create(viewLaunchedFrom: View?) {
- if (fgsManagerDialog == null) {
- fgsManagerDialog = FgsManagerDialog(context, executor, backgroundExecutor,
- systemClock, fgsManagerDialogController)
- fgsManagerDialog!!.setOnDismissListener { i: DialogInterface? ->
- fgsManagerDialogController.onFinishDialog()
- fgsManagerDialog = null
- }
- dialogLaunchAnimator.showFromView(fgsManagerDialog!!, viewLaunchedFrom!!)
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index e24df30..357a68f 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
@@ -104,11 +107,16 @@
public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+ public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, false);
+
/***************************************/
// 600- status bar
public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
new BooleanFlag(601, false);
+ public static final BooleanFlag STATUS_BAR_USER_SWITCHER =
+ new BooleanFlag(602, false);
+
/***************************************/
// 700 - dialer/calls
public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 2ebcd853..f0371fc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -62,7 +62,6 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
@@ -119,6 +118,7 @@
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -327,7 +327,7 @@
TelephonyListenerManager telephonyListenerManager,
GlobalSettings globalSettings,
SecureSettings secureSettings,
- @Nullable Vibrator vibrator,
+ @NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
@@ -397,7 +397,7 @@
mGlobalSettings.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
- mHasVibrator = vibrator != null && vibrator.hasVibrator();
+ mHasVibrator = vibrator.hasVibrator();
mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
R.bool.config_useFixedVolume);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index d1a103e..de67ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -40,6 +40,7 @@
private boolean mIsDropDownMode;
private int mMenuVerticalPadding = 0;
private int mGlobalActionsSidePadding = 0;
+ private int mMaximumWidthThresholdDp = 800;
private ListAdapter mAdapter;
private AdapterView.OnItemLongClickListener mOnItemLongClickListener;
@@ -92,6 +93,8 @@
// width should be between [.5, .9] of screen
int parentWidth = res.getSystem().getDisplayMetrics().widthPixels;
+ float parentDensity = res.getSystem().getDisplayMetrics().density;
+ float parentWidthDp = parentWidth / parentDensity;
int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (parentWidth * 0.9), MeasureSpec.AT_MOST);
int maxWidth = 0;
@@ -101,9 +104,12 @@
int w = child.getMeasuredWidth();
maxWidth = Math.max(w, maxWidth);
}
- int width = Math.max(maxWidth, (int) (parentWidth * 0.5));
- listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding);
+ int width = maxWidth;
+ if (parentWidthDp < mMaximumWidthThresholdDp) {
+ width = Math.max(maxWidth, (int) (parentWidth * 0.5));
+ }
+ listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding);
setWidth(width);
if (getAnchorView().getLayoutDirection() == LayoutDirection.LTR) {
setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width);
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
new file mode 100644
index 0000000..b304c3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.tv.TvBottomSheetActivity;
+
+import javax.inject.Inject;
+
+/**
+ * Confirmation dialog shown when Set Menu Language CEC message was received.
+ */
+public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity
+ implements View.OnClickListener {
+ private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName();
+
+ private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+ @Inject
+ public HdmiCecSetMenuLanguageActivity(
+ HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) {
+ mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper;
+ }
+
+ @Override
+ public final void onCreate(Bundle b) {
+ super.onCreate(b);
+ getWindow().addPrivateFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE);
+ mHdmiCecSetMenuLanguageHelper.setLocale(languageTag);
+ if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title,
+ mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage());
+ CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description);
+ initUI(title, text);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.bottom_sheet_positive_button) {
+ mHdmiCecSetMenuLanguageHelper.acceptLocale();
+ } else {
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ }
+ finish();
+ }
+
+ void initUI(CharSequence title, CharSequence text) {
+ TextView titleTextView = findViewById(R.id.bottom_sheet_title);
+ TextView contentTextView = findViewById(R.id.bottom_sheet_body);
+ ImageView icon = findViewById(R.id.bottom_sheet_icon);
+ ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
+ Button okButton = findViewById(R.id.bottom_sheet_positive_button);
+ Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+ titleTextView.setText(title);
+ contentTextView.setText(text);
+ icon.setImageResource(com.android.internal.R.drawable.ic_settings_language);
+ secondIcon.setVisibility(View.GONE);
+
+ okButton.setText(R.string.hdmi_cec_set_menu_language_accept);
+ okButton.setOnClickListener(this);
+
+ cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline);
+ cancelButton.setOnClickListener(this);
+ cancelButton.requestFocus();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
new file mode 100644
index 0000000..1f58112
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.hdmi;
+
+import android.provider.Settings;
+
+import com.android.internal.app.LocalePicker;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Helper class to separate model and view for system language change initiated by HDMI CEC.
+ */
+@SysUISingleton
+public class HdmiCecSetMenuLanguageHelper {
+ private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName();
+ private static final String SEPARATOR = ",";
+
+ private final Executor mBackgroundExecutor;
+ private final SecureSettings mSecureSettings;
+
+ private Locale mLocale;
+ private HashSet<String> mDenylist;
+
+ @Inject
+ public HdmiCecSetMenuLanguageHelper(@Background Executor executor,
+ SecureSettings secureSettings) {
+ mBackgroundExecutor = executor;
+ mSecureSettings = secureSettings;
+ String denylist = mSecureSettings.getString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST);
+ mDenylist = new HashSet<>(denylist == null
+ ? Collections.EMPTY_SET
+ : Arrays.asList(denylist.split(SEPARATOR)));
+ }
+
+ /**
+ * Set internal locale based on given language tag.
+ */
+ public void setLocale(String languageTag) {
+ mLocale = Locale.forLanguageTag(languageTag);
+ }
+
+ /**
+ * Returns the locale from {@code <Set Menu Language>} CEC message.
+ */
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * Returns whether the locale from {@code <Set Menu Language>} CEC message was already
+ * denylisted.
+ */
+ public boolean isLocaleDenylisted() {
+ return mDenylist.contains(mLocale.toLanguageTag());
+ }
+
+ /**
+ * Accepts the new locale and updates system language.
+ */
+ public void acceptLocale() {
+ mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale));
+ }
+
+ /**
+ * Declines the locale and puts it on the denylist.
+ */
+ public void declineLocale() {
+ mDenylist.add(mLocale.toLanguageTag());
+ mSecureSettings.putString(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
+ String.join(SEPARATOR, mDenylist));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e88011e..88555ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -233,10 +233,13 @@
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
mShellTransitions = shellTransitions;
+ }
- if (shellTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Nothing here. Initialization for this happens in onCreate.
- } else {
+ @Override
+ public void onCreate() {
+ ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+
+ if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
if (sEnableRemoteKeyguardGoingAwayAnimation) {
final RemoteAnimationAdapter exitAnimationAdapter =
@@ -248,22 +251,19 @@
}
if (sEnableRemoteKeyguardOccludeAnimation) {
final RemoteAnimationAdapter occludeAnimationAdapter =
- new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
+ new RemoteAnimationAdapter(
+ mKeyguardViewMediator.getOccludeAnimationRunner(), 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE,
occludeAnimationAdapter);
+
+ final RemoteAnimationAdapter unoccludeAnimationAdapter =
+ new RemoteAnimationAdapter(
+ mKeyguardViewMediator.getUnoccludeAnimationRunner(), 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE,
- occludeAnimationAdapter);
+ unoccludeAnimationAdapter);
}
ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
DEFAULT_DISPLAY, definition);
- }
- }
-
- @Override
- public void onCreate() {
- ((SystemUIApplication) getApplication()).startServicesIfNeeded();
-
- if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
return;
}
if (sEnableRemoteKeyguardGoingAwayAnimation) {
@@ -354,33 +354,6 @@
}
};
- private final IRemoteAnimationRunner.Stub mOccludeAnimationRunner =
- new IRemoteAnimationRunner.Stub() {
- @Override // Binder interface
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- Slog.d(TAG, "mOccludeAnimationRunner.onAnimationStart: transit=" + transit);
- try {
- if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
- mBinder.setOccluded(true /* isOccluded */, true /* animate */);
- } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
- mBinder.setOccluded(false /* isOccluded */, false /* animate */);
- }
- // TODO(bc-unlock): Implement (un)occlude animation.
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException");
- }
- }
-
- @Override // Binder interface
- public void onAnimationCancelled() {
- }
- };
-
final IRemoteTransition mOccludeAnimation = new IRemoteTransition.Stub() {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 08e1654..0f08a18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -77,11 +77,13 @@
import android.view.RemoteAnimationTarget;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
@@ -89,6 +91,7 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardStateCallback;
+import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardConstants;
@@ -102,10 +105,14 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -113,6 +120,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -137,7 +145,7 @@
* state of the keyguard, power management events that effect whether the keyguard
* should be shown or reset, callbacks to the phone window manager to notify
* it of when the keyguard is showing, and events from the keyguard view itself
- * stating that the keyguard was succesfully unlocked.
+ * stating that the keyguard was successfully unlocked.
*
* Note that the keyguard view is shown when the screen is off (as appropriate)
* so that once the screen comes on, it will be ready immediately.
@@ -151,15 +159,15 @@
* - the keyguard is showing
*
* Example external events that translate to keyguard view changes:
- * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * - screen turned off -> reset the keyguard, and show it, so it will be ready
* next time the screen turns on
* - keyboard is slid open -> if the keyguard is not secure, hide it
*
* Events from the keyguard view:
- * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * - user successfully unlocked keyguard -> hide keyguard view, and no longer
* restrict input events.
*
- * Note: in addition to normal power managment events that effect the state of
+ * Note: in addition to normal power management events that effect the state of
* whether the keyguard should be showing, external apps and services may request
* that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
* false, this will override all other conditions for turning on the keyguard.
@@ -223,7 +231,7 @@
/**
* How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
* callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
- * that is reenabling the keyguard.
+ * that is re-enabling the keyguard.
*/
private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
@@ -232,6 +240,7 @@
* keyguard to show even if it is disabled for the current user.
*/
public static final String OPTION_FORCE_SHOW = "force_show";
+ private final DreamOverlayStateController mDreamOverlayStateController;
/** The stream type that the lock sounds are tied to. */
private int mUiSoundsStreamType;
@@ -272,14 +281,14 @@
// these are protected by synchronized (this)
/**
- * External apps (like the phone app) can tell us to disable the keygaurd.
+ * External apps (like the phone app) can tell us to disable the keyguard.
*/
private boolean mExternallyEnabled = true;
/**
* Remember if an external call to {@link #setKeyguardEnabled} with value
* false caused us to hide the keyguard, so that we need to reshow it once
- * the keygaurd is reenabled with another call with value true.
+ * the keyguard is re-enabled with another call with value true.
*/
private boolean mNeedToReshowWhenReenabled = false;
@@ -290,6 +299,9 @@
// AOD is enabled and status bar is in AOD state.
private boolean mAodShowing;
+ // Dream overlay is visible.
+ private boolean mDreamOverlayShowing;
+
/** Cached value of #isInputRestricted */
private boolean mInputRestricted;
@@ -303,7 +315,7 @@
private int mDelayedShowingSequence;
/**
- * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
+ * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case.
*/
private int mDelayedProfileShowingSequence;
@@ -318,6 +330,7 @@
// the properties of the keyguard
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
/**
* Last SIM state reported by the telephony system.
@@ -339,7 +352,7 @@
private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
/**
- * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be
+ * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
* called.
* */
private boolean mHiding;
@@ -353,7 +366,7 @@
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
/**
- * {@link #setKeyguardEnabled} waits on this condition when it reenables
+ * {@link #setKeyguardEnabled} waits on this condition when it re-enables
* the keyguard.
*/
private boolean mWaitingUntilKeyguardVisible = false;
@@ -368,6 +381,9 @@
private int mUnlockSoundId;
private int mTrustedSoundId;
private int mLockSoundStreamId;
+ private final float mPowerButtonY;
+ private final float mWindowCornerRadius;
+
/**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
@@ -468,6 +484,14 @@
}
};
+ private final DreamOverlayStateController.Callback mDreamOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mDreamOverlayShowing = mDreamOverlayStateController.isOverlayActive();
+ }
+ };
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -492,7 +516,7 @@
synchronized (KeyguardViewMediator.this) {
resetKeyguardDonePendingLocked();
if (mLockPatternUtils.isLockScreenDisabled(userId)) {
- // If we switching to a user that has keyguard disabled, dismiss keyguard.
+ // If we are switching to a user that has keyguard disabled, dismiss keyguard.
dismiss(null /* callback */, null /* message */);
} else {
resetStateLocked();
@@ -506,7 +530,7 @@
if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
if (userId != UserHandle.USER_SYSTEM) {
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- // Don't try to dismiss if the user has Pin/Patter/Password set
+ // Don't try to dismiss if the user has Pin/Pattern/Password set
if (info == null || mLockPatternUtils.isSecure(userId)) {
return;
} else if (info.isGuest() || info.isDemo()) {
@@ -800,6 +824,109 @@
}
};
+ /**
+ * Animation launch controller for activities that occlude the keyguard.
+ */
+ private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
+ new ActivityLaunchAnimator.Controller() {
+ @Override
+ public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+ setOccluded(true /* occluded */, false /* animate */);
+ }
+
+ @Override
+ public void onLaunchAnimationCancelled() {
+ setOccluded(true /* occluded */, false /* animate */);
+ }
+
+ @NonNull
+ @Override
+ public ViewGroup getLaunchContainer() {
+ return ((ViewGroup) mKeyguardViewControllerLazy.get()
+ .getViewRootImpl().getView());
+ }
+
+ @Override
+ public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
+ // No-op, launch container is always the shade.
+ Log.wtf(TAG, "Someone tried to change the launch container for the "
+ + "ActivityLaunchAnimator, which should never happen.");
+ }
+
+ @NonNull
+ @Override
+ public LaunchAnimator.State createAnimatorState() {
+ final int width = getLaunchContainer().getWidth();
+ final int height = getLaunchContainer().getHeight();
+
+ final float initialHeight = height / 3f;
+ final float initialWidth = width / 3f;
+
+ if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
+ // Start the animation near the power button, at one-third size, since the
+ // camera was launched from the power button.
+ return new LaunchAnimator.State(
+ (int) (mPowerButtonY - initialHeight / 2f) /* top */,
+ (int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
+ (int) (width - initialWidth) /* left */,
+ width /* right */,
+ mWindowCornerRadius, mWindowCornerRadius);
+ } else {
+ // Start the animation in the center of the screen, scaled down.
+ return new LaunchAnimator.State(
+ height / 2, height / 2, width / 2, width / 2,
+ mWindowCornerRadius, mWindowCornerRadius);
+ }
+ }
+ };
+
+ /**
+ * Animation controller for activities that unocclude the keyguard. This will play the launch
+ * animation in reverse.
+ */
+ private final ActivityLaunchAnimator.Controller mUnoccludeAnimationController =
+ new ActivityLaunchAnimator.Controller() {
+ @Override
+ public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
+ setOccluded(false /* isOccluded */, false /* animate */);
+ }
+
+ @Override
+ public void onLaunchAnimationCancelled() {
+ setOccluded(false /* isOccluded */, false /* animate */);
+ }
+
+ @NonNull
+ @Override
+ public ViewGroup getLaunchContainer() {
+ return ((ViewGroup) mKeyguardViewControllerLazy.get()
+ .getViewRootImpl().getView());
+ }
+
+ @Override
+ public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
+ // No-op, launch container is always the shade.
+ Log.wtf(TAG, "Someone tried to change the launch container for the "
+ + "ActivityLaunchAnimator, which should never happen.");
+ }
+
+ @NonNull
+ @Override
+ public LaunchAnimator.State createAnimatorState() {
+ final int width = getLaunchContainer().getWidth();
+ final int height = getLaunchContainer().getHeight();
+
+ // TODO(b/207399883): Unocclude animation. This currently ends instantly.
+ return new LaunchAnimator.State(
+ 0, height, 0, width, mWindowCornerRadius, mWindowCornerRadius);
+ }
+ };
+
+ private IRemoteAnimationRunner mOccludeAnimationRunner =
+ new ActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);
+ private IRemoteAnimationRunner mUnoccludeAnimationRunner =
+ new ActivityLaunchRemoteAnimationRunner(mUnoccludeAnimationController);
+
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
@@ -809,6 +936,8 @@
private boolean mWallpaperSupportsAmbientMode;
private ScreenOnCoordinator mScreenOnCoordinator;
+ private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -833,7 +962,10 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
+ Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
super(context);
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
@@ -850,6 +982,7 @@
dumpManager.registerDumpable(getClass().getName(), this);
mDeviceConfig = deviceConfig;
mScreenOnCoordinator = screenOnCoordinator;
+ mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
@@ -871,6 +1004,13 @@
mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
+ mDreamOverlayStateController = dreamOverlayStateController;
+
+ mActivityLaunchAnimator = activityLaunchAnimator;
+
+ mPowerButtonY = context.getResources().getDimensionPixelSize(
+ R.dimen.physical_power_button_center_screen_location_y);
+ mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
public void userActivity() {
@@ -976,6 +1116,7 @@
mSystemReady = true;
doKeyguardLocked(null);
mUpdateMonitor.registerCallback(mUpdateCallback);
+ mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
}
// Most services aren't available until the system reaches the ready state, so we
// send it here when the device first boots.
@@ -1037,7 +1178,7 @@
mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
- // Reset keyguard going away state so we can start listening for fingerprint. We
+ // Reset keyguard going away state, so we can start listening for fingerprint. We
// explicitly DO NOT want to call
// mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
// here, since that will mess with the device lock state.
@@ -1117,9 +1258,9 @@
}
private long getLockTimeout(int userId) {
- // if the screen turned off because of timeout or the user hit the power button
+ // if the screen turned off because of timeout or the user hit the power button,
// and we don't need to lock immediately, set an alarm
- // to enable it a little bit later (i.e, give the user a chance
+ // to enable it a bit later (i.e, give the user a chance
// to turn the screen back on within a certain window without
// having to unlock the screen)
final ContentResolver cr = mContext.getContentResolver();
@@ -1213,7 +1354,7 @@
}
/**
- * Let's us know when the device is waking up.
+ * It will let us know when the device is waking up.
*/
public void onStartedWakingUp(boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
@@ -1295,7 +1436,7 @@
if (mExitSecureCallback != null) {
if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
// we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / reenable
+ // can get past the keyguard. ignore extraneous requests to disable / re-enable
return;
}
@@ -1306,7 +1447,7 @@
updateInputRestrictedLocked();
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
- // reenabled after previously hidden, reshow
+ // re-enabled after previously hidden, reshow
if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ "status bar expansion");
mNeedToReshowWhenReenabled = false;
@@ -1324,8 +1465,8 @@
} else {
showLocked(null);
- // block until we know the keygaurd is done drawing (and post a message
- // to unblock us after a timeout so we don't risk blocking too long
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
// and causing an ANR).
mWaitingUntilKeyguardVisible = true;
mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
@@ -1420,6 +1561,14 @@
Trace.endSection();
}
+ public IRemoteAnimationRunner getOccludeAnimationRunner() {
+ return mOccludeAnimationRunner;
+ }
+
+ public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
+ return mUnoccludeAnimationRunner;
+ }
+
public boolean isHiding() {
return mHiding;
}
@@ -1837,10 +1986,14 @@
Trace.beginSection(
"KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
- handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration,
- params.mApps, params.mWallpapers, params.mNonApps,
- params.mFinishedCallback);
- mFalsingCollector.onSuccessfulUnlock();
+ mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
+ () -> {
+ handleStartKeyguardExitAnimation(params.startTime,
+ params.fadeoutDuration,
+ params.mApps, params.mWallpapers, params.mNonApps,
+ params.mFinishedCallback);
+ mFalsingCollector.onSuccessfulUnlock();
+ });
Trace.endSection();
break;
case CANCEL_KEYGUARD_EXIT_ANIM:
@@ -1909,7 +2062,7 @@
mExitSecureCallback = null;
- // after succesfully exiting securely, no need to reshow
+ // after successfully exiting securely, no need to reshow
// the keyguard when they've released the lock
mExternallyEnabled = true;
mNeedToReshowWhenReenabled = false;
@@ -1984,7 +2137,7 @@
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
int id = mLockSounds.play(soundId,
- mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/);
synchronized (this) {
mLockSoundStreamId = id;
}
@@ -2070,7 +2223,7 @@
|| mScreenOnCoordinator.getWakeAndUnlocking()
&& mWallpaperSupportsAmbientMode) {
// When the wallpaper supports ambient mode, the scrim isn't fully opaque during
- // wake and unlock and we should fade in the app on top of the wallpaper
+ // wake and unlock, and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2115,7 +2268,7 @@
Trace.beginSection("KeyguardViewMediator#handleHide");
// It's possible that the device was unlocked in a dream state. It's time to wake up.
- if (mAodShowing) {
+ if (mAodShowing || mDreamOverlayShowing) {
PowerManager pm = mContext.getSystemService(PowerManager.class);
pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:BOUNCER_DOZING");
@@ -2139,10 +2292,12 @@
mKeyguardGoingAwayRunnable.run();
} else {
// TODO(bc-unlock): Fill parameters
- handleStartKeyguardExitAnimation(
- SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
- mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */,
- null /* nonApps */, null /* finishedCallback */);
+ mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(() -> {
+ handleStartKeyguardExitAnimation(
+ SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
+ mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */,
+ null /* nonApps */, null /* finishedCallback */);
+ });
}
}
Trace.endSection();
@@ -2157,15 +2312,15 @@
synchronized (KeyguardViewMediator.this) {
// Tell ActivityManager that we canceled the keyguard animation if
- // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless
- // we're animating the surface behind the keyguard and will be hiding the keyguard
- // shortly.
+ // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
+ // unless we're animating the surface behind the keyguard and will be hiding the
+ // keyguard shortly.
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
if (finishedCallback != null) {
// There will not execute animation, send a finish callback to ensure the remote
- // animation won't hanging there.
+ // animation won't hang there.
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
@@ -2182,7 +2337,7 @@
if (mScreenOnCoordinator.getWakeAndUnlocking()) {
// Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
- // the next draw from here so we don't have to wait for window manager to signal
+ // the next draw from here, so we don't have to wait for window manager to signal
// this to our ViewRootImpl.
mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
mScreenOnCoordinator.setWakeAndUnlocking(false);
@@ -2334,7 +2489,7 @@
}
/**
- * Called if the keyguard exit animation has been cancelled and we should dismiss to the
+ * Called if the keyguard exit animation has been cancelled, and we should dismiss to the
* keyguard.
*
* This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new
@@ -2585,7 +2740,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
* animation.
*
@@ -2599,7 +2754,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
*
* @param apps The list of apps to animate.
@@ -2615,7 +2770,7 @@
}
/**
- * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+ * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove
* the wallpaper and keyguard flag, and start running keyguard exit animation.
*
* @param startTime the start time of the animation in uptime milliseconds. Deprecated.
@@ -2842,4 +2997,36 @@
return mMessage;
}
}
+
+ /**
+ * Implementation of RemoteAnimationRunner that creates a new
+ * {@link ActivityLaunchAnimator.Runner} whenever onAnimationStart is called, delegating the
+ * remote animation methods to that runner.
+ */
+ private class ActivityLaunchRemoteAnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private final ActivityLaunchAnimator.Controller mActivityLaunchController;
+ @Nullable private ActivityLaunchAnimator.Runner mRunner;
+
+ ActivityLaunchRemoteAnimationRunner(ActivityLaunchAnimator.Controller controller) {
+ mActivityLaunchController = controller;
+ }
+
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ if (mRunner != null) {
+ mRunner.onAnimationCancelled();
+ }
+ }
+
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback)
+ throws RemoteException {
+ mRunner = mActivityLaunchAnimator.get().createRunner(mActivityLaunchController);
+ mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
+ }
+ }
}
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/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index dd844e8..195ef1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -32,11 +32,13 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.keyguard.mediator.ScreenOnCoordinator;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -44,6 +46,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -98,7 +101,10 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
+ Lazy<NotificationShadeWindowController> notificationShadeWindowController,
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
return new KeyguardViewMediator(
context,
falsingCollector,
@@ -122,8 +128,10 @@
screenOffAnimationController,
notificationShadeDepthController,
screenOnCoordinator,
- interactionJankMonitor
- );
+ interactionJankMonitor,
+ dreamOverlayStateController,
+ notificationShadeWindowController,
+ activityLaunchAnimator);
}
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index b15807c..6d589aa 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -176,14 +176,9 @@
buffer.removeFirst()
}
buffer.add(message as LogMessageImpl)
- if (systrace) {
- val messageStr = message.printer(message)
- Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "$name - $messageStr")
- }
- if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
- logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
- echo(message)
- }
+ val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
+ logcatEchoTracker.isTagLoggable(message.tag, message.level)
+ echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
}
/** Converts the entire buffer to a newline-delimited string */
@@ -232,8 +227,24 @@
pw.println(message.printer(message))
}
- private fun echo(message: LogMessage) {
- val strMessage = message.printer(message)
+ private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
+ if (toLogcat || toSystrace) {
+ val strMessage = message.printer(message)
+ if (toSystrace) {
+ echoToSystrace(message, strMessage)
+ }
+ if (toLogcat) {
+ echoToLogcat(message, strMessage)
+ }
+ }
+ }
+
+ private fun echoToSystrace(message: LogMessage, strMessage: String) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
+ "$name - ${message.level.shortString} ${message.tag}: $strMessage")
+ }
+
+ private fun echoToLogcat(message: LogMessage, strMessage: String) {
when (message.level) {
LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
LogLevel.DEBUG -> Log.d(message.tag, strMessage)
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/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 44727f2..48f4826 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -159,8 +159,7 @@
}
fun refreshMediaPosition() {
- val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD ||
- statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+ val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
// mediaHost.visible required for proper animations handling
visible = mediaHost.visible &&
!bypassController.bypassEnabled &&
@@ -196,4 +195,4 @@
visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c404f7a..f893f36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -411,7 +411,6 @@
// Returns true if new player is added
private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
- val dataCopy = data.copy(backgroundColor = bgColor)
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
@@ -431,14 +430,14 @@
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
- newPlayer.bindPlayer(dataCopy, key)
+ newPlayer.bindPlayer(data, key)
newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
+ MediaPlayerData.addMediaPlayer(key, data, newPlayer, systemClock)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
} else {
- existingPlayer.bindPlayer(dataCopy, key)
- MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock)
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(key, data, existingPlayer, systemClock)
if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) {
reorderAllPlayers(curVisibleMediaKey)
} else {
@@ -543,7 +542,11 @@
}
private fun getForegroundColor(): Int {
- return context.getColor(android.R.color.system_accent2_900)
+ return if (mediaFlags.useMediaSessionLayout()) {
+ context.getColor(android.R.color.system_neutral2_200)
+ } else {
+ context.getColor(android.R.color.system_accent2_900)
+ }
}
private fun updatePageIndicator() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 69a7ec3..b3e6682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import android.app.PendingIntent;
+import android.app.WallpaperColors;
import android.app.smartspace.SmartspaceAction;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,7 @@
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -54,6 +56,7 @@
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
@@ -405,7 +408,7 @@
seamlessView.setContentDescription(deviceString);
// Dismiss
- mMediaViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+ mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mMediaViewHolder.getDismiss().setEnabled(isDismissible);
mMediaViewHolder.getDismiss().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
@@ -438,11 +441,10 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
// Album art
- PlayerViewHolder playerHolder = (PlayerViewHolder) mMediaViewHolder;
- ImageView albumView = playerHolder.getAlbumView();
+ ImageView albumView = mMediaViewHolder.getAlbumView();
boolean hasArtwork = data.getArtwork() != null;
if (hasArtwork) {
- Drawable artwork = scaleDrawable(data.getArtwork());
+ Drawable artwork = getScaledThumbnail(data.getArtwork());
albumView.setPadding(0, 0, 0, 0);
albumView.setImageDrawable(artwork);
} else {
@@ -548,6 +550,19 @@
/** Bind elements specific to PlayerSessionViewHolder */
private void bindSessionPlayer(@NonNull MediaData data, String key) {
+ // Default colors
+ int surfaceColor = mBackgroundColor;
+ int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+ int textPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+ int textPrimaryInverse = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorPrimaryInverse).getDefaultColor();
+ int textSecondary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorSecondary).getDefaultColor();
+ int textTertiary = com.android.settingslib.Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.textColorTertiary).getDefaultColor();
+
// App icon - use launcher icon
ImageView appIconView = mMediaViewHolder.getAppIcon();
appIconView.clearColorFilter();
@@ -567,26 +582,106 @@
appIconView.setColorFilter(color);
}
+ // Album art
+ ColorScheme colorScheme = null;
+ ImageView albumView = mMediaViewHolder.getAlbumView();
+ boolean hasArtwork = data.getArtwork() != null;
+ if (hasArtwork) {
+ colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()),
+ true);
+
+ // Scale artwork to fit background
+ int width = mMediaViewHolder.getPlayer().getWidth();
+ int height = mMediaViewHolder.getPlayer().getHeight();
+ Drawable artwork = getScaledBackground(data.getArtwork(), width, height);
+ albumView.setPadding(0, 0, 0, 0);
+ albumView.setImageDrawable(artwork);
+ albumView.setClipToOutline(true);
+ } else {
+ // If there's no artwork, use colors from the app icon
+ try {
+ Drawable icon = mContext.getPackageManager().getApplicationIcon(
+ data.getPackageName());
+ colorScheme = new ColorScheme(WallpaperColors.fromDrawable(icon), true);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
+ }
+ }
+
+ // Get colors for player
+ if (colorScheme != null) {
+ surfaceColor = colorScheme.getAccent2().get(9); // A2-800
+ accentPrimary = colorScheme.getAccent1().get(2); // A1-100
+ textPrimary = colorScheme.getNeutral1().get(1); // N1-50
+ textPrimaryInverse = colorScheme.getNeutral1().get(10); // N1-900
+ textSecondary = colorScheme.getNeutral2().get(3); // N2-200
+ textTertiary = colorScheme.getNeutral2().get(5); // N2-400
+ }
+
+ ColorStateList bgColorList = ColorStateList.valueOf(surfaceColor);
+ ColorStateList accentColorList = ColorStateList.valueOf(accentPrimary);
+ ColorStateList textColorList = ColorStateList.valueOf(textPrimary);
+
+ // Gradient and background (visible when there is no art)
+ albumView.setForegroundTintList(ColorStateList.valueOf(surfaceColor));
+ albumView.setBackgroundTintList(
+ ColorStateList.valueOf(surfaceColor));
+ mMediaViewHolder.getPlayer().setBackgroundTintList(bgColorList);
+
+ // Metadata text
+ mMediaViewHolder.getTitleText().setTextColor(textPrimary);
+ mMediaViewHolder.getArtistText().setTextColor(textSecondary);
+
+ // Seekbar
+ SeekBar seekbar = mMediaViewHolder.getSeekBar();
+ seekbar.getThumb().setTintList(textColorList);
+ seekbar.setProgressTintList(textColorList);
+ seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary));
+
+ // Output switcher
+ View seamlessView = mMediaViewHolder.getSeamlessButton();
+ seamlessView.setBackgroundTintList(accentColorList);
+ ImageView seamlessIconView = mMediaViewHolder.getSeamlessIcon();
+ seamlessIconView.setImageTintList(bgColorList);
+ TextView seamlessText = mMediaViewHolder.getSeamlessText();
+ seamlessText.setTextColor(surfaceColor);
+
// Media action buttons
MediaButton semanticActions = data.getSemanticActions();
if (semanticActions != null) {
PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
- setSemanticButton(sessionHolder.getActionPlayPause(),
- semanticActions.getPlayOrPause());
- setSemanticButton(sessionHolder.getActionNext(),
- semanticActions.getNextOrCustom());
- setSemanticButton(sessionHolder.getActionPrev(),
- semanticActions.getPrevOrCustom());
- setSemanticButton(sessionHolder.getActionStart(),
- semanticActions.getStartCustom());
- setSemanticButton(sessionHolder.getActionEnd(),
- semanticActions.getEndCustom());
+
+ // Play/pause button has a background
+ sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
+ setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
+ ColorStateList.valueOf(textPrimaryInverse));
+
+ setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(),
+ textColorList);
+ setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(),
+ textColorList);
} else {
Log.w(TAG, "Using semantic player, but did not get buttons");
}
+
+ // Long press buttons
+ mMediaViewHolder.getLongPressText().setTextColor(textColorList);
+ mMediaViewHolder.getSettingsText().setTextColor(textColorList);
+ mMediaViewHolder.getSettingsText().setBackgroundTintList(accentColorList);
+ mMediaViewHolder.getCancelText().setTextColor(textColorList);
+ mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
+ mMediaViewHolder.getDismissText().setTextColor(textColorList);
+ mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
+
}
- private void setSemanticButton(final ImageButton button, MediaAction mediaAction) {
+ private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
+ ColorStateList fgColor) {
+ button.setImageTintList(fgColor);
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
@@ -844,8 +939,11 @@
mMediaViewController.openGuts();
}
+ /**
+ * Scale drawable to fit into the square album art thumbnail
+ */
@UiThread
- private Drawable scaleDrawable(Icon icon) {
+ private Drawable getScaledThumbnail(Icon icon) {
if (icon == null) {
return null;
}
@@ -870,6 +968,25 @@
}
/**
+ * Scale artwork to fill the background of the panel
+ */
+ @UiThread
+ private Drawable getScaledBackground(Icon icon, int width, int height) {
+ if (icon == null) {
+ return null;
+ }
+ Drawable drawable = icon.loadDrawable(mContext);
+ Rect bounds = new Rect(0, 0, width, height);
+ if (bounds.width() > width || bounds.height() > height) {
+ float offsetX = (bounds.width() - width) / 2.0f;
+ float offsetY = (bounds.height() - height) / 2.0f;
+ bounds.offset((int) -offsetX, (int) -offsetY);
+ }
+ drawable.setBounds(bounds);
+ return drawable;
+ }
+
+ /**
* Get the current media controller
*
* @return the controller
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d926e7d..240ca36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,9 +30,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
-import android.graphics.Canvas
import android.graphics.ImageDecoder
-import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -562,7 +560,7 @@
val mediaController = mediaControllerFactory.create(token)
val metadata = mediaController.metadata
- // Foreground and Background colors computed from album art
+ // Album art
val notif: Notification = sbn.notification
var artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
if (artworkBitmap == null) {
@@ -576,24 +574,6 @@
} else {
Icon.createWithBitmap(artworkBitmap)
}
- if (artWorkIcon != null) {
- // If we have art, get colors from that
- if (artworkBitmap == null) {
- if (artWorkIcon.type == Icon.TYPE_BITMAP ||
- artWorkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP) {
- artworkBitmap = artWorkIcon.bitmap
- } else {
- val drawable: Drawable = artWorkIcon.loadDrawable(context)
- artworkBitmap = Bitmap.createBitmap(
- drawable.intrinsicWidth,
- drawable.intrinsicHeight,
- Bitmap.Config.ARGB_8888)
- val canvas = Canvas(artworkBitmap)
- drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
- drawable.draw(canvas)
- }
- }
- }
// App name
val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -677,7 +657,10 @@
}
val runnable = if (action.actionIntent != null) {
Runnable {
- if (action.isAuthenticationRequired()) {
+ if (action.actionIntent.isActivity) {
+ activityStarter.startPendingIntentDismissingKeyguard(
+ action.actionIntent)
+ } else if (action.isAuthenticationRequired()) {
activityStarter.dismissKeyguardThenExecute({
var result = sendPendingIntent(action.actionIntent)
result
@@ -787,30 +770,28 @@
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
- Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+ Icon.createWithResource(context, R.drawable.ic_media_play),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play)
)
}
PlaybackState.ACTION_PAUSE -> {
MediaAction(
- Icon.createWithResource(context,
- com.android.internal.R.drawable.ic_media_pause),
+ Icon.createWithResource(context, R.drawable.ic_media_pause),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause)
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction(
- Icon.createWithResource(context,
- com.android.internal.R.drawable.ic_media_previous),
+ Icon.createWithResource(context, R.drawable.ic_media_prev),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev)
)
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction(
- Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+ Icon.createWithResource(context, R.drawable.ic_media_next),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next)
)
@@ -900,7 +881,7 @@
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
+ Icon.createWithResource(context, R.drawable.ic_media_play).setTint(themeText),
action,
context.getString(R.string.controls_media_resume)
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index c8cd432..6145f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -31,6 +31,7 @@
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CrossFadeHelper
@@ -82,7 +83,8 @@
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val dreamOverlayStateController: DreamOverlayStateController
) {
/**
@@ -167,7 +169,7 @@
})
}
- private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
/**
* The last location where this view was at before going to the desired location. This is
* useful for guided transitions.
@@ -349,6 +351,17 @@
}
/**
+ * Is the doze animation currently Running
+ */
+ private var dreamOverlayActive: Boolean = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ updateDesiredLocation(forceNoAnimation = true)
+ }
+ }
+
+ /**
* The current cross fade progress. 0.5f means it's just switching
* between the start and the end location and the content is fully faded, while 0.75f means
* that we're halfway faded in again in the target state.
@@ -444,6 +457,12 @@
}
})
+ dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
+ override fun onStateChanged() {
+ dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+ }
+ })
+
wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
goingToSleep = false
@@ -548,8 +567,7 @@
previousLocation = this.desiredLocation
} else if (forceStateUpdate) {
val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD ||
- statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ (statusbarState == StatusBarState.KEYGUARD))
if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
!onLockscreen) {
// If media active state changed and the device is now unlocked, update the
@@ -936,10 +954,10 @@
return desiredLocation
}
val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD ||
- statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ (statusbarState == StatusBarState.KEYGUARD))
val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
val location = when {
+ dreamOverlayActive -> LOCATION_DREAM_OVERLAY
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
!hasActiveMedia -> LOCATION_QS
@@ -1035,6 +1053,11 @@
const val LOCATION_LOCKSCREEN = 2
/**
+ * Attached on the dream overlay
+ */
+ const val LOCATION_DREAM_OVERLAY = 3
+
+ /**
* Attached at the root of the hierarchy in an overlay
*/
const val IN_OVERLAY = -1000
@@ -1062,4 +1085,4 @@
@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
@Retention(AnnotationRetention.SOURCE)
-annotation class MediaLocation
\ No newline at end of file
+annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index c333b50..e57b247 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -35,6 +35,7 @@
val player = itemView as TransitionLayout
// Player information
+ val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
@@ -53,8 +54,9 @@
// Settings screen
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
val cancel = itemView.requireViewById<View>(R.id.cancel)
+ val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text)
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
- val dismissLabel = dismiss.getChildAt(0)
+ val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text)
val settings = itemView.requireViewById<View>(R.id.settings)
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index a1faa40..20b2d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -20,7 +20,6 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
-import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.R
@@ -29,9 +28,6 @@
*/
class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
- // Player information
- val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
-
// Seek bar
val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 4baef3a..663877c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -21,18 +21,22 @@
import android.view.WindowManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+import com.android.systemui.media.nearby.NearbyMediaDevicesService;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -43,11 +47,14 @@
import dagger.multibindings.IntoMap;
/** Dagger module for the media package. */
-@Module
+@Module(subcomponents = {
+ MediaComplicationComponent.class,
+})
public interface MediaModule {
String QS_PANEL = "media_qs_panel";
String QUICK_QS_PANEL = "media_quick_qs_panel";
String KEYGUARD = "media_keyguard";
+ String DREAM = "dream";
/** */
@Provides
@@ -82,14 +89,25 @@
/** */
@Provides
@SysUISingleton
+ @Named(DREAM)
+ static MediaHost providesDreamMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
MediaTttFlags mediaTttFlags,
+ CommandQueue commandQueue,
Context context,
WindowManager windowManager) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(new MediaTttChipControllerSender(context, windowManager));
+ return Optional.of(new MediaTttChipControllerSender(commandQueue, context, windowManager));
}
/** */
@@ -112,6 +130,7 @@
MediaTttFlags mediaTttFlags,
CommandRegistry commandRegistry,
Context context,
+ @Main Executor mainExecutor,
MediaTttChipControllerReceiver mediaTttChipControllerReceiver) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
@@ -120,12 +139,13 @@
new MediaTttCommandLineHelper(
commandRegistry,
context,
+ mainExecutor,
mediaTttChipControllerReceiver));
}
- /** Inject into MediaTttSenderService. */
+ /** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap
- @ClassKey(MediaTttSenderService.class)
- Service bindMediaTttSenderService(MediaTttSenderService service);
+ @ClassKey(NearbyMediaDevicesService.class)
+ Service bindMediaNearbyDevicesService(NearbyMediaDevicesService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
new file mode 100644
index 0000000..65c5bc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static com.android.systemui.media.dagger.MediaModule.DREAM;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+
+import android.widget.FrameLayout;
+
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.MediaHostState;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link MediaComplicationViewController} handles connecting the
+ * {@link com.android.systemui.dreams.complication.Complication} view to the {@link MediaHost}.
+ */
+public class MediaComplicationViewController extends ViewController<FrameLayout> {
+ private final MediaHost mMediaHost;
+
+ @Inject
+ public MediaComplicationViewController(
+ @Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout view,
+ @Named(DREAM) MediaHost mediaHost) {
+ super(view);
+ mMediaHost = mediaHost;
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mMediaHost.setExpansion(MediaHostState.COLLAPSED);
+ mMediaHost.setShowsOnlyActiveMedia(true);
+ mMediaHost.setFalsingProtectionNeeded(true);
+ mMediaHost.init(MediaHierarchyManager.LOCATION_DREAM_OVERLAY);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mMediaHost.hostView.setLayoutParams(new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT));
+ mView.addView(mMediaHost.hostView);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mView.removeView(mMediaHost.hostView);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
new file mode 100644
index 0000000..7c04810
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+
+import javax.inject.Inject;
+
+/**
+ * Media control complication for dream overlay.
+ */
+public class MediaDreamComplication implements Complication {
+ MediaComplicationComponent.Factory mComponentFactory;
+
+ /**
+ * Default constructor for {@link MediaDreamComplication}.
+ */
+ @Inject
+ public MediaDreamComplication(MediaComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_CAST_INFO;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
new file mode 100644
index 0000000..8934cd10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.SmartspaceMediaData;
+
+import javax.inject.Inject;
+
+/**
+ * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering
+ * the media complication as appropriate
+ */
+public class MediaDreamSentinel extends CoreStartable {
+ private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+ private boolean mAdded;
+ @Override
+ public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ if (!mAdded) {
+ return;
+ }
+
+ if (mMediaDataManager.hasActiveMedia()) {
+ return;
+ }
+
+ mAdded = false;
+ mDreamOverlayStateController.removeComplication(mComplication);
+ }
+
+ @Override
+ public void onSmartspaceMediaDataLoaded(@NonNull String key,
+ @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
+ boolean isSsReactivated) {
+ }
+
+ @Override
+ public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
+ @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) {
+ if (mAdded) {
+ return;
+ }
+
+ if (!mMediaDataManager.hasActiveMedia()) {
+ return;
+ }
+
+ mAdded = true;
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ };
+
+ private final MediaDataManager mMediaDataManager;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final MediaDreamComplication mComplication;
+
+ @Inject
+ public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
+ DreamOverlayStateController dreamOverlayStateController,
+ MediaDreamComplication complication) {
+ super(context);
+ mMediaDataManager = mediaDataManager;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mComplication = complication;
+ }
+
+ @Override
+ public void start() {
+ mMediaDataManager.addListener(mListener);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
new file mode 100644
index 0000000..128a38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_LAYOUT_PARAMS;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link Complication.ViewHolder} implementation for media control.
+ */
+public class MediaViewHolder implements Complication.ViewHolder {
+ private final FrameLayout mContainer;
+ private final MediaComplicationViewController mViewController;
+ private final ComplicationLayoutParams mLayoutParams;
+
+ @Inject
+ MediaViewHolder(@Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout container,
+ MediaComplicationViewController controller,
+ @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
+ mContainer = container;
+ mViewController = controller;
+ mViewController.init();
+ mLayoutParams = layoutParams;
+ }
+
+ @Override
+ public View getView() {
+ return mContainer;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
new file mode 100644
index 0000000..3372899
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.media.dream.MediaViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link MediaComplicationComponent} is responsible for generating dependencies surrounding the
+ * media {@link com.android.systemui.dreams.complication.Complication}, such as view controllers
+ * and layout details.
+ */
+@Subcomponent(modules = {
+ MediaComplicationComponent.MediaComplicationModule.class,
+})
+@MediaComplicationComponent.MediaComplicationScope
+public interface MediaComplicationComponent {
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface MediaComplicationScope {}
+
+ /**
+ * Generates {@link MediaComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ MediaComplicationComponent create();
+ }
+
+ /**
+ * Creates {@link MediaViewHolder}.
+ */
+ MediaViewHolder getViewHolder();
+
+ /**
+ * Scoped values for {@link MediaComplicationComponent}.
+ */
+ @Module
+ interface MediaComplicationModule {
+ String MEDIA_COMPLICATION_CONTAINER = "media_complication_container";
+ String MEDIA_COMPLICATION_LAYOUT_PARAMS = "media_complication_layout_params";
+
+ /**
+ * Provides the complication view.
+ */
+ @Provides
+ @MediaComplicationScope
+ @Named(MEDIA_COMPLICATION_CONTAINER)
+ static FrameLayout provideComplicationContainer(Context context) {
+ return new FrameLayout(context);
+ }
+
+ /**
+ * Provides the layout parameters for the complication view.
+ */
+ @Provides
+ @MediaComplicationScope
+ @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_UP,
+ 0,
+ true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
deleted file mode 100644
index 0453fdb..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.nearby
-
-import com.android.systemui.dagger.SysUISingleton
-
-/**
- * A manager that returns information about devices that are nearby and can receive media transfers.
- */
-@SysUISingleton
-class MediaNearbyDevicesManager {
-
- /** Returns a list containing the current nearby devices. */
- fun getCurrentNearbyDevices(): List<NearbyDevice> {
- // TODO(b/216313420): Implement this function.
- return emptyList()
- }
-
- /**
- * Registers [callback] to be notified each time a device's range changes or when a new device
- * comes within range.
- */
- fun registerNearbyDevicesCallback(
- callback: (device: NearbyDevice) -> Unit
- ) {
- // TODO(b/216313420): Implement this function.
- }
-
- /**
- * Un-registers [callback]. See [registerNearbyDevicesCallback].
- */
- fun unregisterNearbyDevicesCallback(
- callback: (device: NearbyDevice) -> Unit
- ) {
- // TODO(b/216313420): Implement this function.
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt
new file mode 100644
index 0000000..eaf2bd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.nearby
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.NearbyDevice
+import javax.inject.Inject
+
+/**
+ * A service that acts as a bridge between (1) external clients that have data on nearby devices
+ * that are able to play media and (2) internal clients (like media Output Switcher) that need data
+ * on these nearby devices.
+ *
+ * TODO(b/216313420): Add logging to this class.
+ */
+@SysUISingleton
+class NearbyMediaDevicesService @Inject constructor() : Service() {
+
+ private var provider: INearbyMediaDevicesProvider? = null
+
+ private val binder: IBinder = object : INearbyMediaDevicesService.Stub() {
+ override fun registerProvider(newProvider: INearbyMediaDevicesProvider) {
+ provider = newProvider
+ newProvider.asBinder().linkToDeath(
+ {
+ // We might've gotten a new provider before the old provider died, so we only
+ // need to clear our provider if the most recent provider died.
+ if (provider == newProvider) {
+ provider = null
+ }
+ },
+ /* flags= */ 0
+ )
+ }
+ }
+
+ override fun onBind(intent: Intent?): IBinder = binder
+
+ /** Returns a list containing the current nearby devices. */
+ fun getCurrentNearbyDevices(): List<NearbyDevice> {
+ val currentProvider = provider ?: return emptyList()
+ return currentProvider.currentNearbyDevices
+ }
+
+ /**
+ * Registers [callback] to be notified each time a device's range changes or when a new device
+ * comes within range.
+ */
+ fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+ val currentProvider = provider ?: return
+ currentProvider.registerNearbyDevicesCallback(callback)
+ }
+
+ /**
+ * Un-registers [callback]. See [registerNearbyDevicesCallback].
+ */
+ fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
+ val currentProvider = provider ?: return
+ currentProvider.unregisterNearbyDevicesCallback(callback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
deleted file mode 100644
index 3c890bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.nearby
-
-import androidx.annotation.IntDef
-import kotlin.annotation.AnnotationRetention
-
-@IntDef(
- RangeZone.RANGE_UNKNOWN,
- RangeZone.RANGE_FAR,
- RangeZone.RANGE_LONG,
- RangeZone.RANGE_CLOSE,
- RangeZone.RANGE_WITHIN_REACH
-)
-@Retention(AnnotationRetention.SOURCE)
-/** The various range zones a device can be in, in relation to the current device. */
-annotation class RangeZone {
- companion object {
- /** Unknown distance range. */
- const val RANGE_UNKNOWN = 0
- /** Distance is very far away from the peer device. */
- const val RANGE_FAR = 1
- /** Distance is relatively long from the peer device, typically a few meters. */
- const val RANGE_LONG = 2
- /** Distance is close to the peer device, typically with one or two meter. */
- const val RANGE_CLOSE = 3
- /** Distance is very close to the peer device, typically within one meter or less. */
- const val RANGE_WITHIN_REACH = 4
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 3720851..1ea21ce 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,34 +16,29 @@
package com.android.systemui.media.taptotransfer
-import android.content.ComponentName
+import android.app.StatusBarManager
import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
-import android.os.IBinder
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
+import com.android.systemui.media.taptotransfer.sender.AlmostCloseToEndCast
+import com.android.systemui.media.taptotransfer.sender.AlmostCloseToStartCast
import com.android.systemui.media.taptotransfer.sender.TransferFailed
import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered
import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded
import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered
import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
+import java.util.concurrent.Executor
import javax.inject.Inject
/**
@@ -54,16 +49,36 @@
class MediaTttCommandLineHelper @Inject constructor(
commandRegistry: CommandRegistry,
private val context: Context,
+ @Main private val mainExecutor: Executor,
private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
) {
- private var senderService: IDeviceSenderService? = null
- private val senderServiceConnection = SenderServiceConnection()
-
private val appIconDrawable =
Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
it.setTint(Color.YELLOW)
}
+ /**
+ * A map from a display state string typed in the command line to the display int it represents.
+ */
+ private val stateStringToStateInt: Map<String, Int> = mapOf(
+ AlmostCloseToStartCast::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ AlmostCloseToEndCast::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ TransferToReceiverTriggered::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ TransferToThisDeviceTriggered::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ TransferToReceiverSucceeded::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ TransferToThisDeviceSucceeded::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ TransferFailed::class.simpleName!!
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ FAR_FROM_RECEIVER_STATE
+ to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER
+ )
+
init {
commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
commandRegistry.registerCommand(
@@ -75,117 +90,63 @@
/** All commands for the sender device. */
inner class SenderCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
- val otherDeviceName = args[0]
- val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
- .addFeature("feature")
- .build()
- val otherDeviceInfo = DeviceInfo(otherDeviceName)
+ val routeInfo = MediaRoute2Info.Builder("id", args[0])
+ .addFeature("feature")
+ .build()
- when (args[1]) {
- MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
- }
- }
- MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
- }
- }
- TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
- }
- }
- TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
- }
- }
- TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> {
- val undoCallback = object : IUndoTransferCallback.Stub() {
- override fun onUndoTriggered() {
- Log.i(TAG, "Undo transfer to receiver callback triggered")
- // The external services that implement this callback would kick off a
- // transfer back to this device, so mimic that here.
- runOnService { senderService ->
- senderService
- .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
- }
- }
- }
- runOnService { senderService ->
- senderService
- .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
- }
- }
- TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> {
- val undoCallback = object : IUndoTransferCallback.Stub() {
- override fun onUndoTriggered() {
- Log.i(TAG, "Undo transfer to this device callback triggered")
- // The external services that implement this callback would kick off a
- // transfer back to the receiver, so mimic that here.
- runOnService { senderService ->
- senderService
- .transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
- }
- }
- }
- runOnService { senderService ->
- senderService
- .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
- }
- }
- TRANSFER_FAILED_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.transferFailed(mediaInfo, otherDeviceInfo)
- }
- }
- NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> {
- runOnService { senderService ->
- senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo)
- context.unbindService(senderServiceConnection)
- }
- }
- else -> {
- pw.println("Sender command must be one of " +
- "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
- "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " +
- "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " +
- "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " +
- "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " +
- "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " +
- "$TRANSFER_FAILED_COMMAND_NAME, " +
- NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
- )
- }
+ @StatusBarManager.MediaTransferSenderState
+ val displayState = stateStringToStateInt[args[1]]
+ if (displayState == null) {
+ pw.println("Invalid command name")
+ return
}
+
+ val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
+ as StatusBarManager
+ statusBarManager.updateMediaTapToTransferSenderDisplay(
+ displayState,
+ routeInfo,
+ getUndoExecutor(displayState),
+ getUndoCallback(displayState)
+ )
+ }
+
+ private fun getUndoExecutor(
+ @StatusBarManager.MediaTransferSenderState displayState: Int
+ ): Executor? {
+ return if (isSucceededState(displayState)) {
+ mainExecutor
+ } else {
+ null
+ }
+ }
+
+ private fun getUndoCallback(
+ @StatusBarManager.MediaTransferSenderState displayState: Int
+ ): Runnable? {
+ return if (isSucceededState(displayState)) {
+ Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+ } else {
+ null
+ }
+ }
+
+ private fun isSucceededState(
+ @StatusBarManager.MediaTransferSenderState displayState: Int
+ ): Boolean {
+ return displayState ==
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ||
+ displayState ==
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>")
- }
-
- private fun runOnService(command: SenderServiceCommand) {
- val currentService = senderService
- if (currentService != null) {
- command.run(currentService)
- } else {
- bindService(command)
- }
- }
-
- private fun bindService(command: SenderServiceCommand) {
- senderServiceConnection.pendingCommand = command
- val binding = context.bindService(
- Intent(context, MediaTttSenderService::class.java),
- senderServiceConnection,
- Context.BIND_AUTO_CREATE
- )
- Log.i(TAG, "Starting service binding? $binding")
+ pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipState>")
}
}
+ // TODO(b/216318437): Migrate the receiver callbacks to StatusBarManager.
+
/** A command to DISPLAY the media ttt chip on the RECEIVER device. */
inner class AddChipCommandReceiver : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
@@ -207,57 +168,20 @@
pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_RECEIVER_TAG")
}
}
-
- /** A service connection for [IDeviceSenderService]. */
- private inner class SenderServiceConnection : ServiceConnection {
- // A command that should be run when the service gets connected.
- var pendingCommand: SenderServiceCommand? = null
-
- override fun onServiceConnected(className: ComponentName, service: IBinder) {
- val newCallback = IDeviceSenderService.Stub.asInterface(service)
- senderService = newCallback
- pendingCommand?.run(newCallback)
- pendingCommand = null
- }
-
- override fun onServiceDisconnected(className: ComponentName) {
- senderService = null
- }
- }
-
- /** An interface defining a command that should be run on the sender service. */
- private fun interface SenderServiceCommand {
- /** Runs the command on the provided [senderService]. */
- fun run(senderService: IDeviceSenderService)
- }
}
@VisibleForTesting
const val SENDER_COMMAND = "media-ttt-chip-sender"
@VisibleForTesting
-const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender"
-@VisibleForTesting
const val ADD_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-add-receiver"
@VisibleForTesting
const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver"
@VisibleForTesting
-val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
-@VisibleForTesting
-val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!!
-@VisibleForTesting
-val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!!
-@VisibleForTesting
-val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME =
- TransferToThisDeviceTriggered::class.simpleName!!
-@VisibleForTesting
-val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!!
-@VisibleForTesting
-val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME =
- TransferToThisDeviceSucceeded::class.simpleName!!
-@VisibleForTesting
-val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!!
-@VisibleForTesting
-val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver"
+val FAR_FROM_RECEIVER_STATE = "FarFromReceiver"
private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon"
-private const val TAG = "MediaTapToTransferCli"
+private const val CLI_TAG = "MediaTransferCli"
+
+private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index c656df2..05baf78 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
+import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.media.taptotransfer.common.MediaTttChipState
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
/**
* A class that stores all the information necessary to display the media tap-to-transfer chip on
@@ -59,7 +59,7 @@
*
* @property otherDeviceName the name of the other device involved in the transfer.
*/
-class MoveCloserToStartCast(
+class AlmostCloseToStartCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
private val otherDeviceName: String,
@@ -76,7 +76,7 @@
*
* @property otherDeviceName the name of the other device involved in the transfer.
*/
-class MoveCloserToEndCast(
+class AlmostCloseToEndCast(
appIconDrawable: Drawable,
appIconContentDescription: String,
private val otherDeviceName: String,
@@ -130,7 +130,7 @@
appIconDrawable: Drawable,
appIconContentDescription: String,
private val otherDeviceName: String,
- val undoCallback: IUndoTransferCallback? = null
+ val undoCallback: IUndoMediaTransferCallback? = null
) : ChipStateSender(appIconDrawable, appIconContentDescription) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
@@ -169,7 +169,7 @@
appIconDrawable: Drawable,
appIconContentDescription: String,
private val otherDeviceName: String,
- val undoCallback: IUndoTransferCallback? = null
+ val undoCallback: IUndoMediaTransferCallback? = null
) : ChipStateSender(appIconDrawable, appIconContentDescription) {
override fun getChipTextString(context: Context): String {
return context.getString(R.string.media_transfer_playing_this_device)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 453e3d6..d1790d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -16,14 +16,21 @@
package com.android.systemui.media.taptotransfer.sender
+import android.app.StatusBarManager
import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.TextView
+import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
+import com.android.systemui.statusbar.CommandQueue
import javax.inject.Inject
/**
@@ -32,11 +39,93 @@
*/
@SysUISingleton
class MediaTttChipControllerSender @Inject constructor(
+ commandQueue: CommandQueue,
context: Context,
windowManager: WindowManager,
) : MediaTttChipControllerCommon<ChipStateSender>(
context, windowManager, R.layout.media_ttt_chip
) {
+ // TODO(b/216141276): Use app icon from media route info instead of this fake one.
+ private val fakeAppIconDrawable =
+ Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
+ it.setTint(Color.YELLOW)
+ }
+
+ private val commandQueueCallbacks = object : CommandQueue.Callbacks {
+ override fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay(
+ displayState, routeInfo, undoCallback
+ )
+ }
+ }
+
+ init {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+
+ private fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ // TODO(b/217418566): This app icon content description is incorrect --
+ // routeInfo.name is the name of the device, not the name of the app.
+ val appIconContentDescription = routeInfo.name.toString()
+ val otherDeviceName = routeInfo.name.toString()
+ val chipState = when(displayState) {
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST ->
+ AlmostCloseToStartCast(
+ fakeAppIconDrawable, appIconContentDescription, otherDeviceName
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST ->
+ AlmostCloseToEndCast(
+ fakeAppIconDrawable, appIconContentDescription, otherDeviceName
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED ->
+ TransferToReceiverTriggered(
+ fakeAppIconDrawable, appIconContentDescription, otherDeviceName
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED ->
+ TransferToThisDeviceTriggered(
+ fakeAppIconDrawable, appIconContentDescription
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ->
+ TransferToReceiverSucceeded(
+ fakeAppIconDrawable,
+ appIconContentDescription,
+ otherDeviceName,
+ undoCallback
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED ->
+ TransferToThisDeviceSucceeded(
+ fakeAppIconDrawable,
+ appIconContentDescription,
+ otherDeviceName,
+ undoCallback
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED ->
+ TransferFailed(
+ fakeAppIconDrawable, appIconContentDescription
+ )
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER -> {
+ removeChip()
+ null
+ }
+ else -> {
+ Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ null
+ }
+ }
+
+ chipState?.let {
+ displayChip(it)
+ }
+ }
/** Displays the chip view for the given state. */
override fun updateChipView(chipState: ChipStateSender, currentChipView: ViewGroup) {
@@ -64,3 +153,5 @@
if (showFailure) { View.VISIBLE } else { View.GONE }
}
}
+
+const val SENDER_TAG = "MediaTapToTransferSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
deleted file mode 100644
index 717752e..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.taptotransfer.sender
-
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.graphics.Color
-import android.graphics.drawable.Icon
-import android.media.MediaRoute2Info
-import android.os.IBinder
-import com.android.systemui.R
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import javax.inject.Inject
-
-/**
- * Service that allows external handlers to trigger the media chip on the sender device.
- */
-class MediaTttSenderService @Inject constructor(
- context: Context,
- val controller: MediaTttChipControllerSender
-) : Service() {
-
- // TODO(b/203800643): Add logging when callbacks trigger.
- private val binder: IBinder = object : IDeviceSenderService.Stub() {
- override fun closeToReceiverToStartCast(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
- }
-
- override fun closeToReceiverToEndCast(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
- }
-
- override fun transferFailed(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.transferFailed(mediaInfo)
- }
-
- override fun transferToReceiverTriggered(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
- }
-
- override fun transferToThisDeviceTriggered(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo)
- }
-
- override fun transferToReceiverSucceeded(
- mediaInfo: MediaRoute2Info,
- otherDeviceInfo: DeviceInfo,
- undoCallback: IUndoTransferCallback
- ) {
- this@MediaTttSenderService.transferToReceiverSucceeded(
- mediaInfo, otherDeviceInfo, undoCallback
- )
- }
-
- override fun transferToThisDeviceSucceeded(
- mediaInfo: MediaRoute2Info,
- otherDeviceInfo: DeviceInfo,
- undoCallback: IUndoTransferCallback
- ) {
- this@MediaTttSenderService.transferToThisDeviceSucceeded(
- mediaInfo, otherDeviceInfo, undoCallback
- )
- }
-
- override fun noLongerCloseToReceiver(
- mediaInfo: MediaRoute2Info,
- otherDeviceInfo: DeviceInfo
- ) {
- this@MediaTttSenderService.noLongerCloseToReceiver()
- }
- }
-
- // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
- private val fakeAppIconDrawable =
- Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
- it.setTint(Color.YELLOW)
- }
-
- override fun onBind(intent: Intent?): IBinder = binder
-
- private fun closeToReceiverToStartCast(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- val chipState = MoveCloserToStartCast(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString(),
- otherDeviceName = otherDeviceInfo.name
- )
- controller.displayChip(chipState)
- }
-
- private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) {
- val chipState = MoveCloserToEndCast(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString(),
- otherDeviceName = otherDeviceInfo.name
- )
- controller.displayChip(chipState)
- }
-
- private fun transferFailed(mediaInfo: MediaRoute2Info) {
- val chipState = TransferFailed(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString()
- )
- controller.displayChip(chipState)
- }
-
- private fun transferToReceiverTriggered(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
- ) {
- val chipState = TransferToReceiverTriggered(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString(),
- otherDeviceName = otherDeviceInfo.name
- )
- controller.displayChip(chipState)
- }
-
- private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) {
- val chipState = TransferToThisDeviceTriggered(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString()
- )
- controller.displayChip(chipState)
- }
-
- private fun transferToReceiverSucceeded(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
- ) {
- val chipState = TransferToReceiverSucceeded(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString(),
- otherDeviceName = otherDeviceInfo.name,
- undoCallback = undoCallback
- )
- controller.displayChip(chipState)
- }
-
- private fun transferToThisDeviceSucceeded(
- mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
- ) {
- val chipState = TransferToThisDeviceSucceeded(
- appIconDrawable = fakeAppIconDrawable,
- appIconContentDescription = mediaInfo.name.toString(),
- otherDeviceName = otherDeviceInfo.name,
- undoCallback = undoCallback
- )
- controller.displayChip(chipState)
- }
-
- private fun noLongerCloseToReceiver() {
- controller.removeChip()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 441e79a..ec15b24 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -31,6 +31,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -304,6 +305,7 @@
allowSystemGestureIgnoringBarVisibility())
.setFlag(SYSUI_STATE_SCREEN_PINNING,
ActivityManagerWrapper.getInstance().isScreenPinningActive())
+ .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
.commitUpdate(mDisplayId);
}
@@ -445,6 +447,10 @@
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
+ private boolean isImmersiveMode() {
+ return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ }
+
@Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 7e5b554..7fb58f0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.SystemClock;
+import android.util.FloatProperty;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.Surface;
@@ -44,6 +45,20 @@
public static final int VERTICAL = 1; // Consume taps along the left edge.
private static final boolean CHATTY = true; // print to logcat when we eat a click
+
+ private static final FloatProperty<DeadZone> FLASH_PROPERTY =
+ new FloatProperty<DeadZone>("DeadZoneFlash") {
+ @Override
+ public void setValue(DeadZone object, float value) {
+ object.setFlash(value);
+ }
+
+ @Override
+ public Float get(DeadZone object) {
+ return object.getFlash();
+ }
+ };
+
private final NavigationBarController mNavBarController;
private final NavigationBarView mNavigationBarView;
@@ -63,7 +78,7 @@
private final Runnable mDebugFlash = new Runnable() {
@Override
public void run() {
- ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ ObjectAnimator.ofFloat(DeadZone.this, FLASH_PROPERTY, 1f, 0f).setDuration(150).start();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 4f4bd1e..be45a62 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -104,7 +104,8 @@
private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
private static final int MAX_NUM_LOGGED_GESTURES = 10;
- static final boolean DEBUG_MISSING_GESTURE = false;
+ // Temporary log until b/202433017 is resolved
+ static final boolean DEBUG_MISSING_GESTURE = true;
static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private ISystemGestureExclusionListener mGestureExclusionListener =
@@ -318,7 +319,11 @@
String recentsPackageName = recentsComponentName.getPackageName();
PackageManager manager = context.getPackageManager();
try {
- Resources resources = manager.getResourcesForApplication(recentsPackageName);
+ Resources resources = manager.getResourcesForApplication(
+ manager.getApplicationInfo(recentsPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.GET_SHARED_LIBRARY_FILES));
int resId = resources.getIdentifier(
"gesture_blocking_activities", "array", recentsPackageName);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index ad4a2f4..dbd641b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -58,7 +58,6 @@
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.SysUISingleton;
@@ -421,7 +420,7 @@
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ mActivityStarter.startActivity(helpIntent,
true /* dismissShade */, resultCode -> {
mHighTempDialog = null;
});
@@ -456,7 +455,7 @@
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ mActivityStarter.startActivity(helpIntent,
true /* dismissShade */, resultCode -> {
mThermalShutdownDialog = null;
});
@@ -516,7 +515,7 @@
helpIntent.setClassName("com.android.settings",
"com.android.settings.HelpTrampoline");
helpIntent.putExtra(Intent.EXTRA_TEXT, contextString);
- Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ mActivityStarter.startActivity(helpIntent,
true /* dismissShade */, resultCode -> {
mUsbHighTempDialog = null;
});
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 37a0f59..642af59 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -43,7 +43,6 @@
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -80,13 +79,13 @@
@VisibleForTesting
final Receiver mReceiver = new Receiver();
- private PowerManager mPowerManager;
- private WarningsUI mWarnings;
+ private final PowerManager mPowerManager;
+ private final WarningsUI mWarnings;
private InattentiveSleepWarningView mOverlayView;
private final Configuration mLastConfiguration = new Configuration();
private int mPlugType = 0;
private int mInvalidCharger = 0;
- private EnhancedEstimates mEnhancedEstimates;
+ private final EnhancedEstimates mEnhancedEstimates;
private Future mLastShowWarningTask;
private boolean mEnableSkinTemperatureWarning;
private boolean mEnableUsbTemperatureAlarm;
@@ -113,18 +112,20 @@
@Inject
public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
- CommandQueue commandQueue, Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
+ CommandQueue commandQueue, Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+ WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+ PowerManager powerManager) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
mStatusBarOptionalLazy = statusBarOptionalLazy;
+ mWarnings = warningsUI;
+ mEnhancedEstimates = enhancedEstimates;
+ mPowerManager = powerManager;
}
public void start() {
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
- mWarnings = Dependency.get(WarningsUI.class);
- mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
mLastConfiguration.setTo(mContext.getResources().getConfiguration());
ContentObserver obs = new ContentObserver(mHandler) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
new file mode 100644
index 0000000..eb341563
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.app.IActivityManager
+import android.app.IForegroundServiceObserver
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.IBinder
+import android.os.PowerExemptionManager
+import android.os.RemoteException
+import android.provider.DeviceConfig.NAMESPACE_SYSTEMUI
+import android.text.format.DateUtils
+import android.util.ArrayMap
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.GuardedBy
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.time.SystemClock
+import java.util.Objects
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.math.max
+
+class FgsManagerController @Inject constructor(
+ private val context: Context,
+ @Main private val mainExecutor: Executor,
+ @Background private val backgroundExecutor: Executor,
+ private val systemClock: SystemClock,
+ private val activityManager: IActivityManager,
+ private val packageManager: PackageManager,
+ private val deviceConfigProxy: DeviceConfigProxy,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
+) : IForegroundServiceObserver.Stub() {
+
+ companion object {
+ private val LOG_TAG = FgsManagerController::class.java.simpleName
+ }
+
+ private var isAvailable = false
+
+ private val lock = Any()
+
+ @GuardedBy("lock")
+ var initialized = false
+
+ @GuardedBy("lock")
+ private val runningServiceTokens = mutableMapOf<UserPackage, StartTimeAndTokens>()
+
+ @GuardedBy("lock")
+ private var dialog: SystemUIDialog? = null
+
+ @GuardedBy("lock")
+ private val appListAdapter: AppListAdapter = AppListAdapter()
+
+ @GuardedBy("lock")
+ private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()
+
+ interface OnNumberOfPackagesChangedListener {
+ fun onNumberOfPackagesChanged(numPackages: Int)
+ }
+
+ interface OnDialogDismissedListener {
+ fun onDialogDismissed()
+ }
+
+ fun init() {
+ synchronized(lock) {
+ if (initialized) {
+ return
+ }
+ try {
+ activityManager.registerForegroundServiceObserver(this)
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+
+ deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+ backgroundExecutor) {
+ isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
+ }
+
+ isAvailable = deviceConfigProxy
+ .getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false)
+
+ initialized = true
+ }
+ }
+
+ override fun onForegroundStateChanged(
+ token: IBinder,
+ packageName: String,
+ userId: Int,
+ isForeground: Boolean
+ ) {
+ synchronized(lock) {
+ val numPackagesBefore = getNumRunningPackagesLocked()
+ val userPackageKey = UserPackage(userId, packageName)
+ if (isForeground) {
+ runningServiceTokens.getOrPut(userPackageKey, { StartTimeAndTokens(systemClock) })
+ .addToken(token)
+ } else {
+ if (runningServiceTokens[userPackageKey]?.also {
+ it.removeToken(token) }?.isEmpty() == true) {
+ runningServiceTokens.remove(userPackageKey)
+ }
+ }
+
+ val numPackagesAfter = getNumRunningPackagesLocked()
+
+ if (numPackagesAfter != numPackagesBefore) {
+ onNumberOfPackagesChangedListeners.forEach {
+ backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) }
+ }
+ }
+
+ updateAppItemsLocked()
+ }
+ }
+
+ @GuardedBy("lock")
+ val onNumberOfPackagesChangedListeners: MutableSet<OnNumberOfPackagesChangedListener> =
+ mutableSetOf()
+
+ @GuardedBy("lock")
+ val onDialogDismissedListeners: MutableSet<OnDialogDismissedListener> = mutableSetOf()
+
+ fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
+ synchronized(lock) {
+ onNumberOfPackagesChangedListeners.add(listener)
+ }
+ }
+
+ fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
+ synchronized(lock) {
+ onNumberOfPackagesChangedListeners.remove(listener)
+ }
+ }
+
+ fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ synchronized(lock) {
+ onDialogDismissedListeners.add(listener)
+ }
+ }
+
+ fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ synchronized(lock) {
+ onDialogDismissedListeners.remove(listener)
+ }
+ }
+
+ fun isAvailable(): Boolean {
+ return isAvailable
+ }
+
+ fun getNumRunningPackages(): Int {
+ synchronized(lock) {
+ return getNumRunningPackagesLocked()
+ }
+ }
+
+ private fun getNumRunningPackagesLocked() =
+ runningServiceTokens.keys.count { it.uiControl != UIControl.HIDE_ENTRY }
+
+ fun shouldUpdateFooterVisibility() = dialog == null
+
+ fun showDialog(viewLaunchedFrom: View?) {
+ synchronized(lock) {
+ if (dialog == null) {
+
+ val dialog = SystemUIDialog(context)
+ dialog.setTitle(R.string.fgs_manager_dialog_title)
+
+ val dialogContext = dialog.context
+
+ val recyclerView = RecyclerView(dialogContext)
+ recyclerView.layoutManager = LinearLayoutManager(dialogContext)
+ recyclerView.adapter = appListAdapter
+
+ dialog.setView(recyclerView)
+
+ this.dialog = dialog
+
+ dialog.setOnDismissListener {
+ synchronized(lock) {
+ this.dialog = null
+ updateAppItemsLocked()
+ }
+ onDialogDismissedListeners.forEach {
+ mainExecutor.execute(it::onDialogDismissed)
+ }
+ }
+
+ mainExecutor.execute {
+ viewLaunchedFrom
+ ?.let { dialogLaunchAnimator.showFromView(dialog, it) } ?: dialog.show()
+ }
+
+ backgroundExecutor.execute {
+ synchronized(lock) {
+ updateAppItemsLocked()
+ }
+ }
+ }
+ }
+ }
+
+ @GuardedBy("lock")
+ private fun updateAppItemsLocked() {
+ if (dialog == null) {
+ runningApps.clear()
+ return
+ }
+
+ val addedPackages = runningServiceTokens.keys.filter {
+ it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
+ }
+ val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
+
+ addedPackages.forEach {
+ val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
+ runningApps[it] = RunningApp(it.userId, it.packageName,
+ runningServiceTokens[it]!!.startTime, it.uiControl,
+ ai.loadLabel(packageManager), ai.loadIcon(packageManager))
+ }
+
+ removedPackages.forEach { pkg ->
+ val ra = runningApps[pkg]!!
+ val ra2 = ra.copy().also {
+ it.stopped = true
+ it.appLabel = ra.appLabel
+ it.icon = ra.icon
+ }
+ runningApps[pkg] = ra2
+ }
+
+ mainExecutor.execute {
+ appListAdapter
+ .setData(runningApps.values.toList().sortedByDescending { it.timeStarted })
+ }
+ }
+
+ private fun stopPackage(userId: Int, packageName: String) {
+ activityManager.stopAppForUser(packageName, userId)
+ }
+
+ private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
+ private val lock = Any()
+
+ @GuardedBy("lock")
+ private var data: List<RunningApp> = listOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
+ return AppItemViewHolder(LayoutInflater.from(parent.context)
+ .inflate(R.layout.fgs_manager_app_item, parent, false))
+ }
+
+ override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
+ var runningApp: RunningApp
+ synchronized(lock) {
+ runningApp = data[position]
+ }
+ with(holder) {
+ iconView.setImageDrawable(runningApp.icon)
+ appLabelView.text = runningApp.appLabel
+ durationView.text = DateUtils.formatDuration(
+ max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
+ DateUtils.LENGTH_MEDIUM)
+ stopButton.setOnClickListener {
+ stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
+ stopPackage(runningApp.userId, runningApp.packageName)
+ }
+ if (runningApp.uiControl == UIControl.HIDE_BUTTON) {
+ stopButton.visibility = View.INVISIBLE
+ }
+ if (runningApp.stopped) {
+ stopButton.isEnabled = false
+ stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
+ durationView.visibility = View.GONE
+ } else {
+ stopButton.isEnabled = true
+ stopButton.setText(R.string.fgs_manager_app_item_stop_button_label)
+ durationView.visibility = View.VISIBLE
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ fun setData(newData: List<RunningApp>) {
+ var oldData = data
+ data = newData
+
+ DiffUtil.calculateDiff(object : DiffUtil.Callback() {
+ override fun getOldListSize(): Int {
+ return oldData.size
+ }
+
+ override fun getNewListSize(): Int {
+ return newData.size
+ }
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int):
+ Boolean {
+ return oldData[oldItemPosition] == newData[newItemPosition]
+ }
+
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int):
+ Boolean {
+ return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped
+ }
+ }).dispatchUpdatesTo(this)
+ }
+ }
+
+ private inner class UserPackage(
+ val userId: Int,
+ val packageName: String
+ ) {
+ val uiControl: UIControl by lazy {
+ val uid = packageManager.getPackageUidAsUser(packageName, userId)
+
+ when (activityManager.getBackgroundRestrictionExemptionReason(uid)) {
+ PowerExemptionManager.REASON_SYSTEM_UID,
+ PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
+
+ PowerExemptionManager.REASON_DEVICE_OWNER,
+ PowerExemptionManager.REASON_PROFILE_OWNER,
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
+ PowerExemptionManager.REASON_ROLE_DIALER,
+ PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
+ else -> UIControl.NORMAL
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is UserPackage) {
+ return false
+ }
+ return other.packageName == packageName && other.userId == userId
+ }
+
+ override fun hashCode(): Int = Objects.hash(userId, packageName)
+ }
+
+ private data class StartTimeAndTokens(
+ val systemClock: SystemClock
+ ) {
+ val startTime = systemClock.elapsedRealtime()
+ val tokens = mutableSetOf<IBinder>()
+
+ fun addToken(token: IBinder) {
+ tokens.add(token)
+ }
+
+ fun removeToken(token: IBinder) {
+ tokens.remove(token)
+ }
+
+ fun isEmpty(): Boolean {
+ return tokens.isEmpty()
+ }
+ }
+
+ private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) {
+ val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label)
+ val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration)
+ val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon)
+ val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button)
+ }
+
+ private data class RunningApp(
+ val userId: Int,
+ val packageName: String,
+ val timeStarted: Long,
+ val uiControl: UIControl
+ ) {
+ constructor(
+ userId: Int,
+ packageName: String,
+ timeStarted: Long,
+ uiControl: UIControl,
+ appLabel: CharSequence,
+ icon: Drawable
+ ) : this(userId, packageName, timeStarted, uiControl) {
+ this.appLabel = appLabel
+ this.icon = icon
+ }
+
+ // variables to keep out of the generated equals()
+ var appLabel: CharSequence = ""
+ var icon: Drawable? = null
+ var stopped = false
+ }
+
+ private enum class UIControl {
+ NORMAL, HIDE_BUTTON, HIDE_ENTRY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 7ac9205..4aedbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -29,14 +29,16 @@
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
-import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.qs.dagger.QSScope
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.phone.SettingsButton
@@ -54,13 +56,14 @@
* Main difference between QS and QQS behaviour is condition when buttons should be visible,
* determined by [buttonsVisibleState]
*/
+@QSScope
class FooterActionsController @Inject constructor(
view: FooterActionsView,
+ multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
private val userTracker: UserTracker,
private val userInfoController: UserInfoController,
- private val multiUserSwitchController: MultiUserSwitchController,
private val deviceProvisionedController: DeviceProvisionedController,
private val falsingManager: FalsingManager,
private val metricsLogger: MetricsLogger,
@@ -68,20 +71,34 @@
private val globalActionsDialog: GlobalActionsDialogLite,
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
- private val buttonsVisibleState: ExpansionState,
private val globalSetting: GlobalSettings,
- private val handler: Handler
+ private val handler: Handler,
+ private val featureFlags: FeatureFlags
) : ViewController<FooterActionsView>(view) {
- enum class ExpansionState { COLLAPSED, EXPANDED }
-
+ private var lastExpansion = -1f
private var listening: Boolean = false
- var expanded = false
+ private val alphaAnimator = TouchAnimator.Builder()
+ .addFloat(mView, "alpha", 0f, 1f)
+ .setStartDelay(0.9f)
+ .build()
+
+ var visible = true
+ set(value) {
+ field = value
+ updateVisibility()
+ }
+
+ init {
+ view.elevation = resources.displayMetrics.density * 4f
+ view.setBackgroundColor(Utils.getColorAttrDefaultColor(context, R.attr.underSurfaceColor))
+ }
private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
+ private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -99,9 +116,8 @@
}
private val onClickListener = View.OnClickListener { v ->
- // Don't do anything until views are unhidden. Don't do anything if the tap looks
- // suspicious.
- if (!buttonsVisible() || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ // Don't do anything if the tap looks suspicious.
+ if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return@OnClickListener
}
if (v === settingsButton) {
@@ -110,9 +126,7 @@
activityStarter.postQSRunnableDismissingKeyguard {}
return@OnClickListener
}
- metricsLogger.action(
- if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
- else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
+ metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
if (settingsButton.isTunerClick) {
activityStarter.postQSRunnableDismissingKeyguard {
if (isTunerEnabled()) {
@@ -135,24 +149,14 @@
}
}
- private fun buttonsVisible(): Boolean {
- return when (buttonsVisibleState) {
- EXPANDED -> expanded
- COLLAPSED -> !expanded
- }
- }
-
override fun onInit() {
multiUserSwitchController.init()
}
- fun hideFooter() {
- mView.visibility = View.GONE
- }
-
- fun showFooter() {
- mView.visibility = View.VISIBLE
- updateView()
+ private fun updateVisibility() {
+ val previousVisibility = mView.visibility
+ mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+ if (previousVisibility != mView.visibility) updateView()
}
private fun startSettingsActivity() {
@@ -204,24 +208,23 @@
}
fun setExpansion(headerExpansionFraction: Float) {
- mView.setExpansion(headerExpansionFraction)
- }
-
- fun updateAnimator(width: Int, numTiles: Int) {
- mView.updateAnimator(width, numTiles)
- }
-
- fun setKeyguardShowing() {
- mView.setKeyguardShowing()
- }
-
- fun refreshVisibility(shouldBeVisible: Boolean) {
- if (shouldBeVisible) {
- showFooter()
+ if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+ if (headerExpansionFraction != lastExpansion) {
+ if (headerExpansionFraction >= 1f) {
+ mView.animate().alpha(1f).setDuration(500L).start()
+ } else if (lastExpansion >= 1f && headerExpansionFraction < 1f) {
+ mView.animate().alpha(0f).setDuration(250L).start()
+ }
+ lastExpansion = headerExpansionFraction
+ }
} else {
- hideFooter()
+ alphaAnimator.setPosition(headerExpansionFraction)
}
}
+ fun setKeyguardShowing(showing: Boolean) {
+ setExpansion(lastExpansion)
+ }
+
private fun isTunerEnabled() = tunerService.isTunerEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
deleted file mode 100644
index 7694be5..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs
-
-import android.os.Handler
-import android.os.UserManager
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.FooterActionsController.ExpansionState
-import com.android.systemui.qs.dagger.QSFlagsModule
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.tuner.TunerService
-import com.android.systemui.util.settings.GlobalSettings
-import javax.inject.Inject
-import javax.inject.Named
-
-class FooterActionsControllerBuilder @Inject constructor(
- private val activityStarter: ActivityStarter,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
- private val userInfoController: UserInfoController,
- private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
- private val deviceProvisionedController: DeviceProvisionedController,
- private val falsingManager: FalsingManager,
- private val metricsLogger: MetricsLogger,
- private val tunerService: TunerService,
- private val globalActionsDialog: GlobalActionsDialogLite,
- private val uiEventLogger: UiEventLogger,
- @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
- private val globalSettings: GlobalSettings,
- @Main private val handler: Handler
-) {
- private lateinit var view: FooterActionsView
- private lateinit var buttonsVisibleState: ExpansionState
-
- fun withView(view: FooterActionsView): FooterActionsControllerBuilder {
- this.view = view
- return this
- }
-
- fun withButtonsVisibleWhen(state: ExpansionState): FooterActionsControllerBuilder {
- buttonsVisibleState = state
- return this
- }
-
- fun build(): FooterActionsController {
- return FooterActionsController(view, activityStarter, userManager,
- userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
- deviceProvisionedController, falsingManager, metricsLogger, tunerService,
- globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
- globalSettings, handler)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index e6fa2ae..18e0cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -44,8 +44,6 @@
private lateinit var multiUserAvatar: ImageView
private lateinit var tunerIcon: View
- private var settingsCogAnimator: TouchAnimator? = null
-
private var qsDisabled = false
private var expansionAmount = 0f
@@ -66,19 +64,6 @@
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
}
- fun updateAnimator(width: Int, numTiles: Int) {
- val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
- mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
- val remaining = (width - numTiles * size) / (numTiles - 1)
- val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
- val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
- settingsCogAnimator = TouchAnimator.Builder()
- .addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
- .addFloat(settingsButton, "rotation", -120f, 0f)
- .build()
- setExpansion(expansionAmount)
- }
-
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateResources()
@@ -95,15 +80,6 @@
tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
}
- fun setKeyguardShowing() {
- setExpansion(expansionAmount)
- }
-
- fun setExpansion(headerExpansionFraction: Float) {
- expansionAmount = headerExpansionFraction
- if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
- }
-
fun disable(
state2: Int,
isTunerEnabled: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index ded6ae0..d1b569f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,9 +14,6 @@
package com.android.systemui.qs;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
-
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.util.Log;
@@ -49,7 +46,6 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Named;
/** */
@QSScope
@@ -88,8 +84,6 @@
private final QSFgsManagerFooter mFgsManagerFooter;
private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
- private final View mQSFooterActions;
- private final View mQQSFooterActions;
@Nullable
private PagedTileLayout mPagedLayout;
@@ -154,16 +148,12 @@
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
@Main Executor executor, TunerService tunerService,
- QSExpansionPathInterpolator qsExpansionPathInterpolator,
- @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
- @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
+ QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mQuickStatusBarHeader = quickStatusBarHeader;
- mQQSFooterActions = qqsFooterActionsView;
- mQSFooterActions = qsFooterActionsView;
mFgsManagerFooter = fgsManagerFooter;
mSecurityFooter = securityFooter;
mHost = qsTileHost;
@@ -476,12 +466,6 @@
.setListener(this)
.build();
- if (mQQSFooterActions.getVisibility() != View.GONE) {
- // only when qqs footer is present (which means split shade mode) it needs to
- // be animated
- updateQQSFooterAnimation();
- }
-
// Fade in the security footer and the divider as we reach the final position
Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
@@ -627,14 +611,6 @@
}
}
- private void updateQQSFooterAnimation() {
- int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions);
- mQQSFooterActionsAnimator = new TouchAnimator.Builder()
- .addFloat(mQQSFooterActions, "translationY", 0, translationY)
- .build();
- mAnimatedQsViews.add(mQSFooterActions);
- }
-
private int getRelativeTranslationY(View view1, View view2) {
int[] qsPosition = new int[2];
int[] qqsPosition = new int[2];
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index e230e1b..7800027 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -19,10 +19,8 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Path;
-import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
@@ -41,7 +39,6 @@
*/
public class QSContainerImpl extends FrameLayout implements Dumpable {
- private final Point mSizePoint = new Point();
private int mFancyClippingTop;
private int mFancyClippingBottom;
private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
@@ -78,12 +75,6 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mSizePoint.set(0, 0); // Will be retrieved on next measure pass.
- }
-
- @Override
public boolean performClick() {
// Want to receive clicks so missing QQS tiles doesn't cause collapse, but
// don't want to do anything with them.
@@ -152,10 +143,10 @@
void updateResources(QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController) {
mQSPanelContainer.setPaddingRelative(
- getPaddingStart(),
+ mQSPanelContainer.getPaddingStart(),
Utils.getQsHeaderSystemIconsAreaHeight(mContext),
- getPaddingEnd(),
- getPaddingBottom()
+ mQSPanelContainer.getPaddingEnd(),
+ mQSPanelContainer.getPaddingBottom()
);
int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
@@ -241,13 +232,6 @@
}
}
- private int getDisplayHeight() {
- if (mSizePoint.y == 0) {
- getDisplay().getRealSize(mSizePoint);
- }
- return mSizePoint.y;
- }
-
/**
* Clip QS bottom using a concave shape.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 082de16..55d4a53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -16,13 +16,9 @@
package com.android.systemui.qs;
-import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
import android.content.Context;
-import android.provider.DeviceConfig;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -30,8 +26,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.fgsmanager.FgsManagerDialogFactory;
-import com.android.systemui.statusbar.policy.RunningFgsController;
import java.util.concurrent.Executor;
@@ -41,24 +35,25 @@
/**
* Footer entry point for the foreground service manager
*/
-public class QSFgsManagerFooter implements View.OnClickListener {
+public class QSFgsManagerFooter implements View.OnClickListener,
+ FgsManagerController.OnDialogDismissedListener,
+ FgsManagerController.OnNumberOfPackagesChangedListener {
private final View mRootView;
private final TextView mFooterText;
private final Context mContext;
private final Executor mMainExecutor;
private final Executor mExecutor;
- private final RunningFgsController mRunningFgsController;
- private final FgsManagerDialogFactory mFgsManagerDialogFactory;
+
+ private final FgsManagerController mFgsManagerController;
private boolean mIsInitialized = false;
- private boolean mIsAvailable = false;
+ private int mNumPackages;
@Inject
QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
- @Main Executor mainExecutor, RunningFgsController runningFgsController,
- @Background Executor executor,
- FgsManagerDialogFactory fgsManagerDialogFactory) {
+ @Main Executor mainExecutor, @Background Executor executor,
+ FgsManagerController fgsManagerController) {
mRootView = rootView;
mFooterText = mRootView.findViewById(R.id.footer_text);
ImageView icon = mRootView.findViewById(R.id.primary_footer_icon);
@@ -66,8 +61,7 @@
mContext = rootView.getContext();
mMainExecutor = mainExecutor;
mExecutor = executor;
- mRunningFgsController = runningFgsController;
- mFgsManagerDialogFactory = fgsManagerDialogFactory;
+ mFgsManagerController = fgsManagerController;
}
public void init() {
@@ -75,22 +69,28 @@
return;
}
+ mFgsManagerController.init();
+
mRootView.setOnClickListener(this);
- mRunningFgsController.addCallback(packages -> refreshState());
-
- DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor,
- (DeviceConfig.OnPropertiesChangedListener) properties -> {
- mIsAvailable = properties.getBoolean(TASK_MANAGER_ENABLED, mIsAvailable);
- });
- mIsAvailable = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, TASK_MANAGER_ENABLED, false);
-
mIsInitialized = true;
}
+ public void setListening(boolean listening) {
+ if (listening) {
+ mFgsManagerController.addOnDialogDismissedListener(this);
+ mFgsManagerController.addOnNumberOfPackagesChangedListener(this);
+ mNumPackages = mFgsManagerController.getNumRunningPackages();
+ refreshState();
+ } else {
+ mFgsManagerController.removeOnDialogDismissedListener(this);
+ mFgsManagerController.removeOnNumberOfPackagesChangedListener(this);
+ }
+ }
+
@Override
public void onClick(View view) {
- mFgsManagerDialogFactory.create(mRootView);
+ mFgsManagerController.showDialog(mRootView);
}
public void refreshState() {
@@ -101,17 +101,25 @@
return mRootView;
}
- private boolean isAvailable() {
- return mIsAvailable;
- }
-
public void handleRefreshState() {
- int numPackages = mRunningFgsController.getPackagesWithFgs().size();
mMainExecutor.execute(() -> {
mFooterText.setText(mContext.getResources().getQuantityString(
- R.plurals.fgs_manager_footer_label, numPackages, numPackages));
- mRootView.setVisibility(numPackages > 0 && isAvailable() ? View.VISIBLE : View.GONE);
+ R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages));
+ if (mFgsManagerController.shouldUpdateFooterVisibility()) {
+ mRootView.setVisibility(mNumPackages > 0
+ && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE);
+ }
});
}
+ @Override
+ public void onDialogDismissed() {
+ refreshState();
+ }
+
+ @Override
+ public void onNumberOfPackagesChanged(int numPackages) {
+ mNumPackages = numPackages;
+ refreshState();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 0e0681b..aac5672 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -42,11 +42,6 @@
*/
void setExpansion(float expansion);
- /**
- * Sets whether or not this footer should set itself to listen for changes in any callbacks
- * that it has implemented.
- */
- void setListening(boolean listening);
/**
* Sets whether or not the keyguard is currently being shown.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 4622660..6c0ca49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -46,7 +46,6 @@
public class QSFooterView extends FrameLayout {
private PageIndicator mPageIndicator;
private TextView mBuildText;
- private View mActionsContainer;
private View mEditButton;
@Nullable
@@ -78,7 +77,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
mPageIndicator = findViewById(R.id.footer_page_indicator);
- mActionsContainer = requireViewById(R.id.qs_footer_actions);
mBuildText = findViewById(R.id.build);
mEditButton = findViewById(android.R.id.edit);
@@ -105,10 +103,6 @@
}
}
- void updateExpansion() {
- setExpansion(mExpansionAmount);
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -129,7 +123,6 @@
@Nullable
private TouchAnimator createFooterAnimator() {
TouchAnimator.Builder builder = new TouchAnimator.Builder()
- .addFloat(mActionsContainer, "alpha", 0, 1)
.addFloat(mPageIndicator, "alpha", 0, 1)
.addFloat(mBuildText, "alpha", 0, 1)
.addFloat(mEditButton, "alpha", 0, 1)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 5327b7e..bef4f43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,8 +16,6 @@
package com.android.systemui.qs;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
-
import android.content.ClipData;
import android.content.ClipboardManager;
import android.text.TextUtils;
@@ -33,7 +31,6 @@
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-import javax.inject.Named;
/**
* Controller for {@link QSFooterView}.
@@ -43,8 +40,6 @@
private final UserTracker mUserTracker;
private final QSPanelController mQsPanelController;
- private final QuickQSPanelController mQuickQSPanelController;
- private final FooterActionsController mFooterActionsController;
private final TextView mBuildText;
private final PageIndicator mPageIndicator;
private final View mEditButton;
@@ -56,14 +51,10 @@
UserTracker userTracker,
FalsingManager falsingManager,
ActivityStarter activityStarter,
- QSPanelController qsPanelController,
- QuickQSPanelController quickQSPanelController,
- @Named(QS_FOOTER) FooterActionsController footerActionsController) {
+ QSPanelController qsPanelController) {
super(view);
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
- mQuickQSPanelController = quickQSPanelController;
- mFooterActionsController = footerActionsController;
mFalsingManager = falsingManager;
mActivityStarter = activityStarter;
@@ -73,21 +64,7 @@
}
@Override
- protected void onInit() {
- super.onInit();
- mFooterActionsController.init();
- }
-
- @Override
protected void onViewAttached() {
- mView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- mView.updateExpansion();
- mFooterActionsController.updateAnimator(right - left,
- mQuickQSPanelController.getNumQuickTiles());
- }
- );
-
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
@@ -114,9 +91,7 @@
}
@Override
- protected void onViewDetached() {
- setListening(false);
- }
+ protected void onViewDetached() {}
@Override
public void setVisibility(int visibility) {
@@ -126,25 +101,17 @@
@Override
public void setExpanded(boolean expanded) {
- mFooterActionsController.setExpanded(expanded);
mView.setExpanded(expanded);
}
@Override
public void setExpansion(float expansion) {
mView.setExpansion(expansion);
- mFooterActionsController.setExpansion(expansion);
- }
-
- @Override
- public void setListening(boolean listening) {
- mFooterActionsController.setListening(listening);
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
mView.setKeyguardShowing();
- mFooterActionsController.setKeyguardShowing();
}
/** */
@@ -156,6 +123,5 @@
@Override
public void disable(int state1, int state2, boolean animate) {
mView.disable(state2);
- mFooterActionsController.disable(state2);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 259b786..50952bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -118,6 +118,7 @@
private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
+ private FooterActionsController mQSFooterActionController;
@Nullable
private ScrollListener mScrollListener;
/**
@@ -188,9 +189,11 @@
QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
mQSPanelController = qsFragmentComponent.getQSPanelController();
mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
+ mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
mQSPanelController.init();
mQuickQSPanelController.init();
+ mQSFooterActionController.init();
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
@@ -380,6 +383,7 @@
mContainer.disable(state1, state2, animate);
mHeader.disable(state1, state2, animate);
mFooter.disable(state1, state2, animate);
+ mQSFooterActionController.disable(state2);
updateQsState();
}
@@ -396,10 +400,10 @@
: View.INVISIBLE);
mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
- mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
- || mShowCollapsedOnKeyguard)
- ? View.VISIBLE
- : View.INVISIBLE);
+ boolean footerVisible = !mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
+ || mShowCollapsedOnKeyguard);
+ mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
+ mQSFooterActionController.setVisible(footerVisible);
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (expanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(
@@ -465,6 +469,7 @@
}
mFooter.setKeyguardShowing(keyguardShowing);
+ mQSFooterActionController.setKeyguardShowing(keyguardShowing);
updateQsState();
}
@@ -480,14 +485,13 @@
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mQSContainerImplController.setListening(listening);
- mFooter.setListening(listening);
+ mQSFooterActionController.setListening(listening);
mQSPanelController.setListening(mListening, mQsExpanded);
}
@Override
public void setHeaderListening(boolean listening) {
mQSContainerImplController.setListening(listening);
- mFooter.setListening(listening);
}
@Override
@@ -561,6 +565,7 @@
}
}
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
+ mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -711,6 +716,7 @@
boolean customizing = isCustomizing();
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
+ mQSFooterActionController.setVisible(!customizing);
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cbfe944..8f268b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -192,6 +192,7 @@
refreshAllTiles();
}
+ mQSFgsManagerFooter.setListening(listening);
mQsSecurityFooter.setListening(listening);
// Set the listening as soon as the QS fragment starts listening regardless of the
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/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 92690c7d..2d2fa1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
-import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import com.android.internal.logging.MetricsLogger;
@@ -54,7 +53,6 @@
// brightness is visible only in split shade
private final QuickQSBrightnessController mBrightnessController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
- private final FooterActionsController mFooterActionsController;
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
@@ -63,14 +61,12 @@
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager,
- QuickQSBrightnessController quickQSBrightnessController,
- @Named(QQS_FOOTER) FooterActionsController footerActionsController
+ QuickQSBrightnessController quickQSBrightnessController
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
mBrightnessController = quickQSBrightnessController;
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
- mFooterActionsController = footerActionsController;
}
@Override
@@ -80,8 +76,6 @@
mMediaHost.setShowsOnlyActiveMedia(true);
mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
mBrightnessController.init(mShouldUseSplitNotificationShade);
- mFooterActionsController.init();
- mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
}
@Override
@@ -102,7 +96,6 @@
void setListening(boolean listening) {
super.setListening(listening);
mBrightnessController.setListening(listening);
- mFooterActionsController.setListening(listening);
}
public boolean isListening() {
@@ -123,7 +116,6 @@
@Override
protected void onConfigurationChanged() {
mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade);
- mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 63cbc21..594f4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.dagger;
+import com.android.systemui.qs.FooterActionsController;
import com.android.systemui.qs.QSAnimator;
import com.android.systemui.qs.QSContainerImplController;
import com.android.systemui.qs.QSFooter;
@@ -61,4 +62,7 @@
/** Construct a {@link QSSquishinessController}. */
QSSquishinessController getQSSquishinessController();
+
+ /** Construct a {@link FooterActionsController}. */
+ FooterActionsController getQSFooterActionController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 1958caf..776ee10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -21,15 +21,15 @@
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewStub;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.qs.FooterActionsController;
-import com.android.systemui.qs.FooterActionsController.ExpansionState;
-import com.android.systemui.qs.FooterActionsControllerBuilder;
import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
@@ -55,8 +55,6 @@
public interface QSFragmentModule {
String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
- String QQS_FOOTER = "qqs_footer";
- String QS_FOOTER = "qs_footer";
String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
/**
@@ -122,46 +120,26 @@
return view.findViewById(R.id.qs_footer);
}
- /** */
+ /**
+ * Provides a {@link FooterActionsView}.
+ *
+ * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
+ */
@Provides
- @Named(QS_FOOTER)
- static FooterActionsView providesQSFooterActionsView(@RootView View view) {
+ static FooterActionsView providesQSFooterActionsView(@RootView View view,
+ FeatureFlags featureFlags) {
+ ViewStub stub;
+ if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+ stub = view.requireViewById(R.id.container_stub);
+ } else {
+ stub = view.requireViewById(R.id.footer_stub);
+ }
+ stub.inflate();
return view.findViewById(R.id.qs_footer_actions);
}
/** */
@Provides
- @Named(QQS_FOOTER)
- static FooterActionsView providesQQSFooterActionsView(@RootView View view) {
- return view.findViewById(R.id.qqs_footer_actions);
- }
-
- /** */
- @Provides
- @Named(QQS_FOOTER)
- static FooterActionsController providesQQSFooterActionsController(
- FooterActionsControllerBuilder footerActionsControllerBuilder,
- @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
- return footerActionsControllerBuilder
- .withView(qqsFooterActionsView)
- .withButtonsVisibleWhen(ExpansionState.COLLAPSED)
- .build();
- }
-
- /** */
- @Provides
- @Named(QS_FOOTER)
- static FooterActionsController providesQSFooterActionsController(
- FooterActionsControllerBuilder footerActionsControllerBuilder,
- @Named(QS_FOOTER) FooterActionsView qsFooterActionsView) {
- return footerActionsControllerBuilder
- .withView(qsFooterActionsView)
- .withButtonsVisibleWhen(ExpansionState.EXPANDED)
- .build();
- }
-
- /** */
- @Provides
@QSScope
static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
qsFooterViewController.init();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 48255b5..6d1bbee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -34,8 +34,6 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.RunningFgsController;
-import com.android.systemui.statusbar.policy.RunningFgsControllerImpl;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
@@ -91,9 +89,4 @@
/** */
@Binds
QSHost provideQsHost(QSTileHost controllerImpl);
-
- /** */
- @Binds
- RunningFgsController provideRunningFgsController(
- RunningFgsControllerImpl runningFgsController);
}
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/OverlayActionChip.java
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
rename to packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 6c01f0e..c4ea67e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -16,12 +16,13 @@
package com.android.systemui.screenshot;
+import static java.util.Objects.requireNonNull;
+
import android.app.PendingIntent;
import android.content.Context;
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;
@@ -29,10 +30,11 @@
import com.android.systemui.R;
+
/**
* View for a chip with an icon and text.
*/
-public class ScreenshotActionChip extends FrameLayout {
+public class OverlayActionChip extends FrameLayout {
private static final String TAG = "ScreenshotActionChip";
@@ -40,27 +42,27 @@
private TextView mTextView;
private boolean mIsPending = false;
- public ScreenshotActionChip(Context context) {
+ public OverlayActionChip(Context context) {
this(context, null);
}
- public ScreenshotActionChip(Context context, AttributeSet attrs) {
+ public OverlayActionChip(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+ public OverlayActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public ScreenshotActionChip(
+ public OverlayActionChip(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
- mIconView = findViewById(R.id.screenshot_action_chip_icon);
- mTextView = findViewById(R.id.screenshot_action_chip_text);
+ mIconView = requireNonNull(findViewById(R.id.overlay_action_chip_icon));
+ mTextView = requireNonNull(findViewById(R.id.overlay_action_chip_text));
updatePadding(mTextView.getText().length() > 0);
}
@@ -117,19 +119,18 @@
(LinearLayout.LayoutParams) mTextView.getLayoutParams();
if (hasText) {
int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_padding_horizontal);
+ R.dimen.overlay_action_chip_padding_horizontal);
int spacing = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_spacing);
+ R.dimen.overlay_action_chip_spacing);
iconParams.setMarginStart(paddingHorizontal);
iconParams.setMarginEnd(spacing);
textParams.setMarginEnd(paddingHorizontal);
} else {
int paddingHorizontal = mContext.getResources().getDimensionPixelSize(
- R.dimen.screenshot_action_chip_icon_only_padding_horizontal);
+ R.dimen.overlay_action_chip_icon_only_padding_horizontal);
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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 83d8d19..30456a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -344,7 +344,7 @@
};
mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
ClipboardOverlayController.COPY_OVERLAY_ACTION),
- ClipboardOverlayController.SELF_PERMISSION, null);
+ ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
}
void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e5649a1..f982790 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -131,9 +131,9 @@
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
private final DisplayMetrics mDisplayMetrics;
- private final float mCornerSizeX;
- private final float mDismissDeltaY;
+ private final float mFixedSize;
private final AccessibilityManager mAccessibilityManager;
+ private final GestureDetector mSwipeDetector;
private int mNavMode;
private boolean mOrientationPortrait;
@@ -151,23 +151,21 @@
private LinearLayout mActionsView;
private ImageView mBackgroundProtection;
private FrameLayout mDismissButton;
- private ScreenshotActionChip mShareChip;
- private ScreenshotActionChip mEditChip;
- private ScreenshotActionChip mScrollChip;
- private ScreenshotActionChip mQuickShareChip;
+ private OverlayActionChip mShareChip;
+ private OverlayActionChip mEditChip;
+ private OverlayActionChip mScrollChip;
+ private OverlayActionChip mQuickShareChip;
private UiEventLogger mUiEventLogger;
private ScreenshotViewCallback mCallbacks;
- private Animator mDismissAnimation;
private boolean mPendingSharedTransition;
- private GestureDetector mSwipeDetector;
private SwipeDismissHandler mSwipeDismissHandler;
private InputMonitorCompat mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private boolean mShowScrollablePreview;
private String mPackageName = "";
- private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
+ private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
private enum PendingInteraction {
@@ -194,9 +192,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
mResources = mContext.getResources();
- mCornerSizeX = mResources.getDimensionPixelSize(R.dimen.screenshot_x_scale);
- mDismissDeltaY = mResources.getDimensionPixelSize(
- R.dimen.screenshot_dismissal_height_delta);
+ mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
// standard material ease
mFastOutSlowIn =
@@ -474,16 +470,14 @@
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
updateInsets(insets);
- int screenshotFixedSize =
- mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
if (mOrientationPortrait) {
- params.width = screenshotFixedSize;
+ params.width = (int) mFixedSize;
params.height = LayoutParams.WRAP_CONTENT;
mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
} else {
params.width = LayoutParams.WRAP_CONTENT;
- params.height = screenshotFixedSize;
+ params.height = (int) mFixedSize;
mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
}
@@ -500,7 +494,7 @@
// ratio of preview width, end vs. start size
float cornerScale =
- mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
+ mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
final float currentScale = 1 / cornerScale;
AnimatorSet dropInAnimation = new AnimatorSet();
@@ -651,7 +645,7 @@
} catch (RemoteException e) {
}
- ArrayList<ScreenshotActionChip> chips = new ArrayList<>();
+ ArrayList<OverlayActionChip> chips = new ArrayList<>();
mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
@@ -716,7 +710,7 @@
+ (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
mActionsContainer.setScaleX(containerScale);
mActionsContainerBackground.setScaleX(containerScale);
- for (ScreenshotActionChip chip : chips) {
+ for (OverlayActionChip chip : chips) {
chip.setAlpha(t);
chip.setScaleX(1 / containerScale); // invert to keep size of children constant
}
@@ -772,8 +766,8 @@
LayoutInflater inflater = LayoutInflater.from(mContext);
for (Notification.Action smartAction : imageData.smartActions) {
- ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.screenshot_action_chip, mActionsView, false);
+ OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
+ R.layout.overlay_action_chip, mActionsView, false);
actionChip.setText(smartAction.title);
actionChip.setIcon(smartAction.getIcon(), false);
actionChip.setPendingIntent(smartAction.actionIntent,
@@ -792,8 +786,8 @@
void addQuickShareChip(Notification.Action quickShareAction) {
if (mPendingInteraction == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
- mQuickShareChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.screenshot_action_chip, mActionsView, false);
+ mQuickShareChip = (OverlayActionChip) inflater.inflate(
+ R.layout.overlay_action_chip, mActionsView, false);
mQuickShareChip.setText(quickShareAction.title);
mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
mQuickShareChip.setOnClickListener(v -> {
@@ -894,7 +888,7 @@
if (mShowScrollablePreview) {
Rect scrollableArea = scrollableAreaOnScreen(response);
- float scale = mCornerSizeX
+ float scale = mFixedSize
/ (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
ConstraintLayout.LayoutParams params =
(ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
@@ -945,7 +939,7 @@
}
boolean isDismissing() {
- return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ return mSwipeDismissHandler.isDismissing();
}
boolean isPendingSharedTransition() {
@@ -961,12 +955,6 @@
Log.d(TAG, "reset screenshot view");
}
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "cancelling dismiss animation");
- }
- mDismissAnimation.cancel();
- }
mSwipeDismissHandler.cancel();
if (DEBUG_WINDOW) {
Log.d(TAG, "removing OnComputeInternalInsetsListener");
@@ -994,7 +982,7 @@
mShareChip.setIsPending(false);
mEditChip.setIsPending(false);
mPendingInteraction = null;
- for (ScreenshotActionChip chip : mSmartChips) {
+ for (OverlayActionChip chip : mSmartChips) {
mActionsView.removeView(chip);
}
mSmartChips.clear();
@@ -1019,31 +1007,6 @@
}
}
- private AnimatorSet createScreenshotTranslateDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
- alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
- alphaAnim.addUpdateListener(animation -> {
- setAlpha(1 - animation.getAnimatedFraction());
- });
-
- ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
- xAnim.setInterpolator(mAccelerateInterpolator);
- xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
- float deltaX = mDirectionLTR
- ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
- : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
- xAnim.addUpdateListener(animation -> {
- float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
- mScreenshotStatic.setTranslationX(currXDelta);
- });
-
- AnimatorSet animSet = new AnimatorSet();
- animSet.play(xAnim).with(alphaAnim);
-
- return animSet;
- }
-
ValueAnimator createScreenshotFadeDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
index 4e96003..451fb13 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import android.animation.Animator;
@@ -137,10 +138,20 @@
}
/**
+ * Return whether the view is currently being dismissed
+ */
+ public boolean isDismissing() {
+ return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ }
+
+ /**
* Cancel the currently-running dismissal animation, if any.
*/
public void cancel() {
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ if (isDismissing()) {
+ if (DEBUG_ANIM) {
+ Log.d(TAG, "cancelling dismiss animation");
+ }
mDismissAnimation.cancel();
}
}
@@ -182,7 +193,13 @@
// make sure the UI gets all the way off the screen in the direction of movement
// (the actions container background is guaranteed to be both the leftmost and
// rightmost UI element in LTR and RTL)
- float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels;
+ float finalX;
+ int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection();
+ if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) {
+ finalX = mDisplayMetrics.widthPixels;
+ } else {
+ finalX = -1 * mView.getRight();
+ }
float distance = Math.abs(finalX - startX);
anim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 597e424..2f5eaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -37,11 +37,13 @@
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
+import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -62,6 +64,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.GcUtils;
import com.android.internal.view.AppearanceRegion;
@@ -154,6 +157,9 @@
private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
+ private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
+ private static final int MSG_MEDIA_TRANSFER_SENDER_STATE = 64 << MSG_SHIFT;
+ private static final int MSG_MEDIA_TRANSFER_RECEIVER_STATE = 65 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -317,6 +323,12 @@
}
/**
+ * @see IStatusBar#setBiometicContextListener(IBiometricContextListener)
+ */
+ default void setBiometicContextListener(IBiometricContextListener listener) {
+ }
+
+ /**
* @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
*/
default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
@@ -431,6 +443,17 @@
* @see IStatusBar#cancelRequestAddTile
*/
default void cancelRequestAddTile(@NonNull String packageName) {}
+
+ /** @see IStatusBar#updateMediaTapToTransferSenderDisplay */
+ default void updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState int displayState,
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable IUndoMediaTransferCallback undoCallback) {}
+
+ /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */
+ default void updateMediaTapToTransferReceiverDisplay(
+ @StatusBarManager.MediaTransferReceiverState int displayState,
+ @NonNull MediaRoute2Info routeInfo) {}
}
public CommandQueue(Context context) {
@@ -958,6 +981,13 @@
}
@Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SET_BIOMETRICS_LISTENER, listener).sendToTarget();
+ }
+ }
+
+ @Override
public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
@@ -1162,6 +1192,29 @@
mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_CANCEL, s).sendToTarget();
}
+ @Override
+ public void updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState int displayState,
+ MediaRoute2Info routeInfo,
+ IUndoMediaTransferCallback undoCallback
+ ) throws RemoteException {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = displayState;
+ args.arg2 = routeInfo;
+ args.arg3 = undoCallback;
+ mHandler.obtainMessage(MSG_MEDIA_TRANSFER_SENDER_STATE, args).sendToTarget();
+ }
+
+ @Override
+ public void updateMediaTapToTransferReceiverDisplay(
+ int displayState,
+ MediaRoute2Info routeInfo) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = displayState;
+ args.arg2 = routeInfo;
+ mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1411,6 +1464,12 @@
mCallbacks.get(i).hideAuthenticationDialog();
}
break;
+ case MSG_SET_BIOMETRICS_LISTENER:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setBiometicContextListener(
+ (IBiometricContextListener) msg.obj);
+ }
+ break;
case MSG_SET_UDFPS_HBM_LISTENER:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
@@ -1553,6 +1612,29 @@
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).cancelRequestAddTile(packageName);
}
+ break;
+ case MSG_MEDIA_TRANSFER_SENDER_STATE:
+ args = (SomeArgs) msg.obj;
+ int displayState = (int) args.arg1;
+ MediaRoute2Info routeInfo = (MediaRoute2Info) args.arg2;
+ IUndoMediaTransferCallback undoCallback =
+ (IUndoMediaTransferCallback) args.arg3;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).updateMediaTapToTransferSenderDisplay(
+ displayState, routeInfo, undoCallback);
+ }
+ args.recycle();
+ break;
+ case MSG_MEDIA_TRANSFER_RECEIVER_STATE:
+ args = (SomeArgs) msg.obj;
+ int receiverDisplayState = (int) args.arg1;
+ MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay(
+ receiverDisplayState, receiverRouteInfo);
+ }
+ args.recycle();
+ break;
}
}
}
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/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c136d9c..b312ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -337,11 +337,11 @@
if (field != value || forceApplyAmount) {
field = value
if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
- nsslController.setTransitionToFullShadeAmount(field)
+ qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ nsslController.setTransitionToFullShadeAmount(field, qSDragProgress)
+ qS.setTransitionToFullShadeAmount(field, qSDragProgress)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
- qS.setTransitionToFullShadeAmount(field, qSDragProgress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 4adf2bc..ebd610b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -19,6 +19,7 @@
import android.graphics.Region;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
@@ -192,6 +193,14 @@
default void setLightRevealScrimOpaque(boolean opaque) {}
/**
+ * Defer any application of window {@link WindowManager.LayoutParams} until {@code scope} is
+ * fully applied.
+ */
+ default void batchApplyWindowLayoutParams(@NonNull Runnable scope) {
+ scope.run();
+ }
+
+ /**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
* collapsed if not for the plugin.
* TODO: Find cleaner way to do this.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 51a66aa..3411eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -45,6 +48,7 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.Utils;
/**
* A notification shelf view that is placed inside the notification scroller. It manages the
@@ -81,6 +85,11 @@
private int mIndexOfFirstViewInShelf = -1;
private float mCornerAnimationDistance;
private NotificationShelfController mController;
+ private int mActualWidth = -1;
+ private boolean mUseSplitShade;
+
+ /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
+ private float mFractionToShade;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -122,13 +131,16 @@
layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
setLayoutParams(layoutParams);
- int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+ final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ // TODO(b/213480466) enable short shelf on split shade
+ mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources());
+
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
setVisibility(GONE);
@@ -203,6 +215,10 @@
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
viewState.yTranslation = stackEnd - viewState.height;
+
+ final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN);
+ final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade);
+ updateStateWidth(viewState, fraction, shortestWidth);
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -211,6 +227,77 @@
}
/**
+ * @param shelfState View state for NotificationShelf
+ * @param fraction Fraction of lockscreen to shade transition
+ * @param shortestWidth Shortest width to use for lockscreen shelf
+ */
+ @VisibleForTesting
+ public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) {
+ shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard()
+ ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction)
+ : getWidth();
+ }
+
+ /**
+ * @param fractionToShade Fraction of lockscreen to shade transition
+ */
+ public void setFractionToShade(float fractionToShade) {
+ mFractionToShade = fractionToShade;
+ }
+
+ /**
+ * @return Actual width of shelf, accounting for possible ongoing width animation
+ */
+ public int getActualWidth() {
+ return mActualWidth > -1 ? mActualWidth : getWidth();
+ }
+
+ /**
+ * @param localX Click x from left of screen
+ * @param slop Margin of error within which we count x for valid click
+ * @param left Left of shelf, from left of screen
+ * @param right Right of shelf, from left of screen
+ * @return Whether click x was in view
+ */
+ @VisibleForTesting
+ public boolean isXInView(float localX, float slop, float left, float right) {
+ return (left - slop) <= localX && localX < (right + slop);
+ }
+
+ /**
+ * @param localY Click y from top of shelf
+ * @param slop Margin of error within which we count y for valid click
+ * @param top Top of shelf
+ * @param bottom Height of shelf
+ * @return Whether click y was in view
+ */
+ @VisibleForTesting
+ public boolean isYInView(float localY, float slop, float top, float bottom) {
+ return (top - slop) <= localY && localY < (bottom + slop);
+ }
+
+ /**
+ * @param localX Click x
+ * @param localY Click y
+ * @param slop Margin of error for valid click
+ * @return Whether this click was on the visible (non-clipped) part of the shelf
+ */
+ @Override
+ public boolean pointInView(float localX, float localY, float slop) {
+ final float containerWidth = getWidth();
+ final float shelfWidth = getActualWidth();
+
+ final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0;
+ final float right = isLayoutRtl() ? containerWidth : shelfWidth;
+
+ final float top = mClipTopAmount;
+ final float bottom = getActualHeight();
+
+ return isXInView(localX, slop, left, right)
+ && isYInView(localY, slop, top, bottom);
+ }
+
+ /**
* Update the shelf appearance based on the other notifications around it. This transforms
* the icons from the notification area into the shelf.
*/
@@ -732,11 +819,15 @@
// we always want to clip to our sides, such that nothing can draw outside of these bounds
int height = getResources().getDisplayMetrics().heightPixels;
mClipRect.set(0, -height, getWidth(), height);
- mShelfIcons.setClipBounds(mClipRect);
+ if (mShelfIcons != null) {
+ mShelfIcons.setClipBounds(mClipRect);
+ }
}
private void updateRelativeOffset() {
- mCollapsedIcons.getLocationOnScreen(mTmp);
+ if (mCollapsedIcons != null) {
+ mCollapsedIcons.getLocationOnScreen(mTmp);
+ }
getLocationOnScreen(mTmp);
}
@@ -831,9 +922,20 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
- private class ShelfState extends ExpandableViewState {
+ public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
+ public int actualWidth = -1;
+
+ private void updateShelfWidth(View view) {
+ if (actualWidth < 0) {
+ return;
+ }
+ mActualWidth = actualWidth;
+ ActivatableNotificationView anv = (ActivatableNotificationView) view;
+ anv.getBackgroundNormal().setActualWidth(actualWidth);
+ mShelfIcons.setActualLayoutWidth(actualWidth);
+ }
@Override
public void applyToView(View view) {
@@ -846,19 +948,21 @@
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
@Override
- public void animateTo(View child, AnimationProperties properties) {
+ public void animateTo(View view, AnimationProperties properties) {
if (!mShowNotificationShelf) {
return;
}
- super.animateTo(child, properties);
+ super.animateTo(view, properties);
setIndexOfFirstViewInShelf(firstViewInShelf);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
+ updateShelfWidth(view);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index abfdfaf..092e86d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -47,6 +48,7 @@
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.wm.shell.bubbles.Bubbles;
@@ -98,6 +100,8 @@
private final ForegroundServiceSectionController mFgsSectionController;
private final NotifPipelineFlags mNotifPipelineFlags;
private AssistantFeedbackController mAssistantFeedbackController;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final Context mContext;
private NotificationPresenter mPresenter;
@@ -129,7 +133,9 @@
DynamicChildBindController dynamicChildBindController,
LowPriorityInflationHelper lowPriorityInflationHelper,
AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags) {
+ NotifPipelineFlags notifPipelineFlags,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mHandler = mainHandler;
mFeatureFlags = featureFlags;
@@ -149,6 +155,8 @@
mDynamicChildBindController = dynamicChildBindController;
mLowPriorityInflationHelper = lowPriorityInflationHelper;
mAssistantFeedbackController = assistantFeedbackController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -171,9 +179,15 @@
if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
return;
}
+ Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews");
beginUpdate();
+ boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
+ && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+ KeyguardUpdateMonitor.getCurrentUser()))
+ && !mKeyguardStateController.isKeyguardGoingAway();
List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
@@ -192,7 +206,7 @@
boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
boolean userPublic = devicePublic
|| mLockscreenUserManager.isLockscreenPublicMode(userId);
- if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked()
+ if (userPublic && dynamicallyUnlocked
&& (userId == currentUserId || userId == UserHandle.USER_ALL
|| !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
userPublic = false;
@@ -340,6 +354,7 @@
mListContainer.onNotificationViewUpdateFinished();
endUpdate();
+ Trace.endSection();
}
/**
@@ -349,6 +364,7 @@
* {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
*/
private void updateNotifStats() {
+ Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats");
boolean hasNonClearableAlertingNotifs = false;
boolean hasClearableAlertingNotifs = false;
boolean hasNonClearableSilentNotifs = false;
@@ -390,6 +406,7 @@
hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
hasClearableSilentNotifs /* hasClearableSilentNotifs */
));
+ Trace.endSection();
}
/**
@@ -507,7 +524,7 @@
}
private void updateRowStatesInternal() {
- Trace.beginSection("NotificationViewHierarchyManager#updateRowStates");
+ Trace.beginSection("NotificationViewHierarchyManager.updateRowStates");
final int N = mListContainer.getContainerChildCount();
int visibleNotifications = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 5302188..4a7606c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -33,7 +34,6 @@
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Parcelable;
@@ -57,6 +57,7 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.util.drawable.DrawableSize;
import java.text.NumberFormat;
import java.util.Arrays;
@@ -84,16 +85,6 @@
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
- /**
- * Maximum allowed byte count for an icon bitmap
- * @see android.graphics.RecordingCanvas.MAX_BITMAP_SIZE
- */
- private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
- /**
- * Maximum allowed width or height for an icon drawable, if we can't get byte count
- */
- private static final int MAX_IMAGE_SIZE = 5000;
-
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -390,21 +381,6 @@
return false;
}
- if (drawable instanceof BitmapDrawable && ((BitmapDrawable) drawable).getBitmap() != null) {
- // If it's a bitmap we can check the size directly
- int byteCount = ((BitmapDrawable) drawable).getBitmap().getByteCount();
- if (byteCount > MAX_BITMAP_SIZE) {
- Log.w(TAG, "Drawable is too large (" + byteCount + " bytes) " + mIcon);
- return false;
- }
- } else if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE
- || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) {
- // Otherwise, check dimensions
- Log.w(TAG, "Drawable is too large (" + drawable.getIntrinsicWidth() + "x"
- + drawable.getIntrinsicHeight() + ") " + mIcon);
- return false;
- }
-
if (withClear) {
setImageDrawable(null);
}
@@ -432,7 +408,7 @@
* @return Drawable for this item, or null if the package or item could not
* be found
*/
- public static Drawable getIcon(Context sysuiContext,
+ private Drawable getIcon(Context sysuiContext,
Context context, StatusBarIcon statusBarIcon) {
int userId = statusBarIcon.user.getIdentifier();
if (userId == UserHandle.USER_ALL) {
@@ -446,6 +422,16 @@
typedValue, true);
float scaleFactor = typedValue.getFloat();
+ // We downscale the loaded drawable to reasonable size to protect against applications
+ // using too much memory. The size can be tweaked in config.xml. Drawables
+ // that are already sized properly won't be touched.
+ boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+ Resources res = sysuiContext.getResources();
+ int maxIconSize = res.getDimensionPixelSize(isLowRamDevice
+ ? com.android.internal.R.dimen.notification_small_icon_size_low_ram
+ : com.android.internal.R.dimen.notification_small_icon_size);
+ icon = DrawableSize.downscaleToSize(res, icon, maxIconSize, maxIconSize);
+
// No need to scale the icon, so return it as is.
if (scaleFactor == 1.f) {
return icon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
index c0148c0..16bc951e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
@@ -37,12 +37,6 @@
*/
public static final int SHADE_LOCKED = 2;
- /**
- * Status bar is locked and shows the full screen user switcher.
- */
- public static final int FULLSCREEN_USER_SWITCHER = 3;
-
-
public static String toShortString(int x) {
switch (x) {
case SHADE:
@@ -51,8 +45,6 @@
return "SHD_LCK";
case KEYGUARD:
return "KGRD";
- case FULLSCREEN_USER_SWITCHER:
- return "FS_USRSW";
default:
return "bad_value_" + x;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index bd948ece..ee12cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -76,7 +76,7 @@
// Must be a power of 2
private static final int HISTORY_SIZE = 32;
- private static final int MAX_STATE = StatusBarState.FULLSCREEN_USER_SWITCHER;
+ private static final int MAX_STATE = StatusBarState.SHADE_LOCKED;
private static final int MIN_STATE = StatusBarState.SHADE;
private static final Comparator<RankedListener> sComparator =
@@ -518,6 +518,7 @@
}
private void recordHistoricalState(int newState, int lastState, boolean upcoming) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState);
mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
HistoricalState state = mHistoricalRecords[mHistoryIndex];
state.mNewState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
index 8330169..b66a48e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
@@ -34,10 +34,7 @@
STATUS_BAR_STATE_KEYGUARD(430),
@UiEvent(doc = "StatusBarState changed to SHADE_LOCKED state")
- STATUS_BAR_STATE_SHADE_LOCKED(431),
-
- @UiEvent(doc = "StatusBarState changed to FULLSCREEN_USER_SWITCHER state")
- STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER(432);
+ STATUS_BAR_STATE_SHADE_LOCKED(431);
private int mId;
StatusBarStateEvent(int id) {
@@ -60,8 +57,6 @@
return STATUS_BAR_STATE_KEYGUARD;
case StatusBarState.SHADE_LOCKED:
return STATUS_BAR_STATE_SHADE_LOCKED;
- case StatusBarState.FULLSCREEN_USER_SWITCHER:
- return STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER;
default:
return STATUS_BAR_STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 6c3a909..c74621d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -16,13 +16,19 @@
package com.android.systemui.statusbar;
-import android.content.Context;
-import android.os.AsyncTask;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -32,21 +38,75 @@
public class VibratorHelper {
private final Vibrator mVibrator;
- private final Context mContext;
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ private final Executor mExecutor;
/**
*/
@Inject
- public VibratorHelper(Context context) {
- mContext = context;
- mVibrator = context.getSystemService(Vibrator.class);
+ public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+ mExecutor = executor;
+ mVibrator = vibrator;
}
+ /**
+ * @see Vibrator#vibrate(long)
+ */
public void vibrate(final int effectId) {
- AsyncTask.execute(() ->
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() ->
mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */),
TOUCH_VIBRATION_ATTRIBUTES));
}
+
+ /**
+ * @see Vibrator#vibrate(int, String, VibrationEffect, String, VibrationAttributes)
+ */
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
+ String reason, @NonNull VibrationAttributes attributes) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(uid, opPkg, vibe, reason, attributes));
+ }
+
+ /**
+ * @see Vibrator#vibrate(VibrationEffect, AudioAttributes)
+ */
+ public void vibrate(@NonNull VibrationEffect effect, @NonNull AudioAttributes attributes) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(effect, attributes));
+ }
+
+ /**
+ * @see Vibrator#vibrate(VibrationEffect)
+ */
+ public void vibrate(@NotNull VibrationEffect effect) {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(() -> mVibrator.vibrate(effect));
+ }
+
+ /**
+ * @see Vibrator#hasVibrator()
+ */
+ public boolean hasVibrator() {
+ return mVibrator != null && mVibrator.hasVibrator();
+ }
+
+ /**
+ * @see Vibrator#cancel()
+ */
+ public void cancel() {
+ if (!hasVibrator()) {
+ return;
+ }
+ mExecutor.execute(mVibrator::cancel);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d574cda..e3d0d98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -23,6 +23,7 @@
import android.service.dreams.IDreamManager;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
@@ -72,6 +73,7 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
@@ -214,7 +216,9 @@
DynamicChildBindController dynamicChildBindController,
LowPriorityInflationHelper lowPriorityInflationHelper,
AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags) {
+ NotifPipelineFlags notifPipelineFlags,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController) {
return new NotificationViewHierarchyManager(
context,
mainHandler,
@@ -231,7 +235,9 @@
dynamicChildBindController,
lowPriorityInflationHelper,
assistantFeedbackController,
- notifPipelineFlags);
+ notifPipelineFlags,
+ keyguardUpdateMonitor,
+ keyguardStateController);
}
/**
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/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index c331608..ad9f12e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -24,6 +24,7 @@
import android.app.NotificationChannel;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -343,11 +344,14 @@
private final InflationCallback mInflationCallback = new InflationCallback() {
@Override
public void handleInflationException(NotificationEntry entry, Exception e) {
+ Trace.beginSection("NotificationEntryManager.handleInflationException");
NotificationEntryManager.this.handleInflationException(entry.getSbn(), e);
+ Trace.endSection();
}
@Override
public void onAsyncInflationFinished(NotificationEntry entry) {
+ Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished");
mPendingNotifications.remove(entry.getKey());
// If there was an async task started after the removal, we don't want to add it back to
// the list, otherwise we might get leaks.
@@ -369,6 +373,7 @@
}
}
}
+ Trace.endSection();
}
};
@@ -463,6 +468,7 @@
boolean forceRemove,
DismissedByUserStats dismissedByUserStats,
int reason) {
+ Trace.beginSection("NotificationEntryManager.removeNotificationInternal");
final NotificationEntry entry = getActiveNotificationUnfiltered(key);
@@ -470,6 +476,7 @@
if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
// Remove intercepted; log and skip
mLogger.logRemovalIntercepted(key);
+ Trace.endSection();
return;
}
}
@@ -557,6 +564,7 @@
mLeakDetector.trackGarbage(entry);
}
}
+ Trace.endSection();
}
private void sendNotificationRemovalToServer(
@@ -620,6 +628,7 @@
private void addNotificationInternal(
StatusBarNotification notification,
RankingMap rankingMap) throws InflationException {
+ Trace.beginSection("NotificationEntryManager.addNotificationInternal");
String key = notification.getKey();
if (DEBUG) {
Log.d(TAG, "addNotification key=" + key);
@@ -667,6 +676,7 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
+ Trace.endSection();
}
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -679,12 +689,14 @@
private void updateNotificationInternal(StatusBarNotification notification,
RankingMap ranking) throws InflationException {
+ Trace.beginSection("NotificationEntryManager.updateNotificationInternal");
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
abortExistingInflation(key, "updateNotification");
final NotificationEntry entry = getActiveNotificationUnfiltered(key);
if (entry == null) {
+ Trace.endSection();
return;
}
@@ -721,6 +733,7 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
+ Trace.endSection();
}
public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -740,14 +753,17 @@
mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
return;
}
+ Trace.beginSection("NotificationEntryManager.updateNotifications");
reapplyFilterAndSort(reason);
if (mPresenter != null) {
mPresenter.updateNotificationViews(reason);
}
mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
+ Trace.endSection();
}
public void updateNotificationRanking(RankingMap rankingMap) {
+ Trace.beginSection("NotificationEntryManager.updateNotificationRanking");
List<NotificationEntry> entries = new ArrayList<>();
entries.addAll(getVisibleNotifications());
entries.addAll(mPendingNotifications.values());
@@ -788,6 +804,7 @@
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
+ Trace.endSection();
}
void notifyChannelModified(
@@ -887,6 +904,7 @@
/** @return list of active notifications filtered for the current user */
public List<NotificationEntry> getActiveNotificationsForCurrentUser() {
+ Trace.beginSection("NotificationEntryManager.getActiveNotificationsForCurrentUser");
Assert.isMainThread();
ArrayList<NotificationEntry> filtered = new ArrayList<>();
@@ -898,7 +916,7 @@
}
filtered.add(entry);
}
-
+ Trace.endSection();
return filtered;
}
@@ -908,10 +926,12 @@
* @param reason the reason for calling this method, which will be logged
*/
public void updateRanking(RankingMap rankingMap, String reason) {
+ Trace.beginSection("NotificationEntryManager.updateRanking");
updateRankingAndSort(rankingMap, reason);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
+ Trace.endSection();
}
/** Resorts / filters the current notification set with the current RankingMap */
@@ -920,7 +940,9 @@
mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
return;
}
+ Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort");
updateRankingAndSort(mRanker.getRankingMap(), reason);
+ Trace.endSection();
}
/** Calls to NotificationRankingManager and updates mSortedAndFiltered */
@@ -929,9 +951,11 @@
mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
return;
}
+ Trace.beginSection("NotificationEntryManager.updateRankingAndSort");
mSortedAndFiltered.clear();
mSortedAndFiltered.addAll(mRanker.updateRanking(
rankingMap, mActiveNotifications.values(), reason));
+ Trace.endSection();
}
/** dump the current active notification list. Called from StatusBar */
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..b328ae8 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
@@ -226,8 +226,13 @@
mNotifSections.clear();
for (NotifSectioner sectioner : sectioners) {
- mNotifSections.add(new NotifSection(sectioner, mNotifSections.size()));
+ final NotifSection section = new NotifSection(sectioner, mNotifSections.size());
+ final NotifComparator sectionComparator = section.getComparator();
+ mNotifSections.add(section);
sectioner.setInvalidationListener(this::onNotifSectionInvalidated);
+ if (sectionComparator != null) {
+ sectionComparator.setInvalidationListener(this::onNotifComparatorInvalidated);
+ }
}
mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size()));
@@ -359,6 +364,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
@@ -419,14 +431,18 @@
}
Trace.endSection();
+ Trace.beginSection("ShadeListBuilder.logEndBuildList");
// Step 9: We're done!
mLogger.logEndBuildList(
mIterationCount,
mReadOnlyNotifList.size(),
countChildren(mReadOnlyNotifList));
if (mAlwaysLogList || mIterationCount % 10 == 0) {
+ Trace.beginSection("ShadeListBuilder.logFinalList");
mLogger.logFinalList(mNotifList);
+ Trace.endSection();
}
+ Trace.endSection();
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
Trace.endSection();
@@ -667,6 +683,12 @@
// having its summary promoted, regardless of how many children it has
Set<String> groupsWithChildrenLostToStability =
getGroupsWithChildrenLostToStability(shadeList);
+ // Like groups which lost a child to stability, any group which lost a child to promotion
+ // is exempt from having its summary promoted when it has no attached children.
+ Set<String> groupsWithChildrenLostToPromotionOrStability =
+ getGroupsWithChildrenLostToPromotion(shadeList);
+ groupsWithChildrenLostToPromotionOrStability.addAll(groupsWithChildrenLostToStability);
+
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -676,9 +698,9 @@
final boolean hasSummary = group.getSummary() != null;
if (hasSummary && children.size() == 0) {
- if (groupsWithChildrenLostToStability.contains(group.getKey())) {
- // This group lost a child on this run to stability, so it is exempt from
- // having its summary promoted to the top level, so prune it.
+ if (groupsWithChildrenLostToPromotionOrStability.contains(group.getKey())) {
+ // This group lost a child on this run to promotion or stability, so it is
+ // exempt from having its summary promoted to the top level, so prune it.
// It has no children, so it will just vanish.
pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i);
} else {
@@ -819,6 +841,26 @@
}
/**
+ * Collect the keys of any groups which have already lost a child to a {@link NotifPromoter}
+ * this run.
+ *
+ * These groups will be exempt from appearing without any children.
+ */
+ @NonNull
+ private Set<String> getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList) {
+ ArraySet<String> groupsWithChildrenLostToPromotion = new ArraySet<>();
+ for (int i = 0; i < shadeList.size(); i++) {
+ final ListEntry tle = shadeList.get(i);
+ if (tle.getAttachState().getPromoter() != null) {
+ // This top-level-entry was part of a group, but was promoted out of it.
+ final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey();
+ groupsWithChildrenLostToPromotion.add(groupKey);
+ }
+ }
+ return groupsWithChildrenLostToPromotion;
+ }
+
+ /**
* If a ListEntry was added to the shade list and then later removed (e.g. because it was a
* group that was broken up), this method will erase any bookkeeping traces of that addition
* and/or check that they were already erased.
@@ -963,16 +1005,20 @@
}
private void freeEmptyGroups() {
+ Trace.beginSection("ShadeListBuilder.freeEmptyGroups");
mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
+ Trace.endSection();
}
private void logChanges() {
+ Trace.beginSection("ShadeListBuilder.logChanges");
for (NotificationEntry entry : mAllEntries) {
logAttachStateChanges(entry);
}
for (GroupEntry group : mGroups.values()) {
logAttachStateChanges(group);
}
+ Trace.endSection();
}
private void logAttachStateChanges(ListEntry entry) {
@@ -1050,16 +1096,23 @@
}
private void cleanupPluggables() {
+ Trace.beginSection("ShadeListBuilder.cleanupPluggables");
callOnCleanup(mNotifPreGroupFilters);
callOnCleanup(mNotifPromoters);
callOnCleanup(mNotifFinalizeFilters);
callOnCleanup(mNotifComparators);
for (int i = 0; i < mNotifSections.size(); i++) {
- mNotifSections.get(i).getSectioner().onCleanup();
+ final NotifSection notifSection = mNotifSections.get(i);
+ notifSection.getSectioner().onCleanup();
+ final NotifComparator comparator = notifSection.getComparator();
+ if (comparator != null) {
+ comparator.onCleanup();
+ }
}
callOnCleanup(List.of(getStabilityManager()));
+ Trace.endSection();
}
private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
@@ -1068,6 +1121,19 @@
}
}
+ @Nullable
+ private NotifComparator getSectionComparator(
+ @NonNull ListEntry o1, @NonNull ListEntry o2) {
+ final NotifSection section = o1.getSection();
+ if (section != o2.getSection()) {
+ throw new RuntimeException("Entry ordering should only be done within sections");
+ }
+ if (section != null) {
+ return section.getComparator();
+ }
+ return null;
+ }
+
private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
int cmp = Integer.compare(
o1.getSectionIndex(),
@@ -1079,6 +1145,12 @@
cmp = Integer.compare(index1, index2);
if (cmp != 0) return cmp;
+ NotifComparator sectionComparator = getSectionComparator(o1, o2);
+ if (sectionComparator != null) {
+ cmp = sectionComparator.compare(o1, o2);
+ if (cmp != 0) return cmp;
+ }
+
for (int i = 0; i < mNotifComparators.size(); i++) {
cmp = mNotifComparators.get(i).compare(o1, o2);
if (cmp != 0) return cmp;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index ba88ad7..a390e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -51,23 +51,20 @@
val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
override fun isInSection(entry: ListEntry): Boolean =
isConversation(entry)
+
+ override fun getComparator() = object : NotifComparator("People") {
+ override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
+ val type1 = getPeopleType(entry1)
+ val type2 = getPeopleType(entry2)
+ return type2.compareTo(type1)
+ }
+ }
+
override fun getHeaderNodeController() =
// TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
}
- val comparator = object : NotifComparator("People") {
- override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
- assert(entry1.section === entry2.section)
- if (entry1.section?.sectioner !== sectioner) {
- return 0
- }
- val type1 = getPeopleType(entry1)
- val type2 = getPeopleType(entry2)
- return type2.compareTo(type1)
- }
- }
-
override fun attach(pipeline: NotifPipeline) {
pipeline.addPromoter(notificationPromoter)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 0bf21af..41b0706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -15,13 +15,18 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification
+import android.app.Notification.GROUP_ALERT_SUMMARY
+import android.util.ArrayMap
import android.util.ArraySet
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -29,13 +34,13 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
/**
@@ -54,27 +59,283 @@
*/
@CoordinatorScope
class HeadsUpCoordinator @Inject constructor(
+ private val mLogger: HeadsUpCoordinatorLogger,
+ private val mSystemClock: SystemClock,
private val mHeadsUpManager: HeadsUpManager,
private val mHeadsUpViewBinder: HeadsUpViewBinder,
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
@IncomingHeader private val mIncomingHeaderController: NodeController,
- @Main private val mExecutor: DelayableExecutor
+ @Main private val mExecutor: DelayableExecutor,
) : Coordinator {
+ private val mEntriesBindingUntil = ArrayMap<String, Long>()
private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
+ private lateinit var mNotifPipeline: NotifPipeline
+ private var mNow: Long = -1
// notifs we've extended the lifetime for
private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>()
override fun attach(pipeline: NotifPipeline) {
+ mNotifPipeline = pipeline
mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
pipeline.addCollectionListener(mNotifCollectionListener)
+ pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups)
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
pipeline.addPromoter(mNotifPromoter)
pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
}
private fun onHeadsUpViewBound(entry: NotificationEntry) {
mHeadsUpManager.showNotification(entry)
+ mEntriesBindingUntil.remove(entry.key)
+ }
+
+ /**
+ * Once the pipeline starts running, we can look through posted entries and quickly process
+ * any that don't have groups, and thus will never gave a group alert edge case.
+ */
+ fun onBeforeTransformGroups(list: List<ListEntry>) {
+ mNow = mSystemClock.currentTimeMillis()
+ if (mPostedEntries.isEmpty()) {
+ return
+ }
+ // Process all non-group adds/updates
+ mPostedEntries.values.toList().forEach { posted ->
+ if (!posted.entry.sbn.isGroup) {
+ handlePostedEntry(posted, "non-group")
+ mPostedEntries.remove(posted.key)
+ }
+ }
+ }
+
+ /**
+ * Once we have a nearly final shade list (not including what's pruned for inflation reasons),
+ * we know that stability and [NotifPromoter]s have been applied, so we can use the location of
+ * notifications in this list to determine what kind of group alert behavior should happen.
+ */
+ fun onBeforeFinalizeFilter(list: List<ListEntry>) {
+ // Nothing to do if there are no other adds/updates
+ if (mPostedEntries.isEmpty()) {
+ return
+ }
+ // Calculate a bunch of information about the logical group and the locations of group
+ // entries in the nearly-finalized shade list. These may be used in the per-group loop.
+ val postedEntriesByGroup = mPostedEntries.values.groupBy { it.entry.sbn.groupKey }
+ val logicalMembersByGroup = mNotifPipeline.allNotifs.asSequence()
+ .filter { postedEntriesByGroup.contains(it.sbn.groupKey) }
+ .groupBy { it.sbn.groupKey }
+ val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) }
+ mLogger.logEvaluatingGroups(postedEntriesByGroup.size)
+ // For each group, determine which notification(s) for a group should alert.
+ postedEntriesByGroup.forEach { (groupKey, postedEntries) ->
+ // get and classify the logical members
+ val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList()
+ val logicalSummary = logicalMembers.find { it.sbn.notification.isGroupSummary }
+
+ // Report the start of this group's evaluation
+ mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size)
+
+ // If there is no logical summary, then there is no alert to transfer
+ if (logicalSummary == null) {
+ postedEntries.forEach { handlePostedEntry(it, "logical-summary-missing") }
+ return@forEach
+ }
+
+ // If summary isn't wanted to be heads up, then there is no alert to transfer
+ if (!isGoingToShowHunStrict(logicalSummary)) {
+ postedEntries.forEach { handlePostedEntry(it, "logical-summary-not-alerting") }
+ return@forEach
+ }
+
+ // The group is alerting! Overall goals:
+ // - Maybe transfer its alert to a child
+ // - Also let any/all newly alerting children still alert
+ var childToReceiveParentAlert: NotificationEntry?
+ var targetType = "undefined"
+
+ // If the parent is alerting, always look at the posted notification with the newest
+ // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the
+ // parent's alert.
+ childToReceiveParentAlert =
+ findAlertOverride(postedEntries, groupLocationsByKey::getLocation)
+ if (childToReceiveParentAlert != null) {
+ targetType = "alertOverride"
+ }
+
+ // If the summary is Detached and we have not picked a receiver of the alert, then we
+ // need to look for the best child to alert in place of the summary.
+ val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key)
+ if (!isSummaryAttached && childToReceiveParentAlert == null) {
+ childToReceiveParentAlert =
+ findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation)
+ if (childToReceiveParentAlert != null) {
+ targetType = "bestChild"
+ }
+ }
+
+ // If there is no child to receive the parent alert, then just handle the posted entries
+ // and return.
+ if (childToReceiveParentAlert == null) {
+ postedEntries.forEach { handlePostedEntry(it, "no-transfer-target") }
+ return@forEach
+ }
+
+ // At this point we just need to initiate the transfer
+ val summaryUpdate = mPostedEntries[logicalSummary.key]
+
+ // If the summary was not attached, then remove the alert from the detached summary.
+ // Otherwise we can simply ignore its posted update.
+ if (!isSummaryAttached) {
+ val summaryUpdateForRemoval = summaryUpdate?.also {
+ it.shouldHeadsUpEver = false
+ } ?: PostedEntry(logicalSummary,
+ wasAdded = false,
+ wasUpdated = false,
+ shouldHeadsUpEver = false,
+ shouldHeadsUpAgain = false,
+ isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+ isBinding = isEntryBinding(logicalSummary),
+ )
+ // If we transfer the alert and the summary isn't even attached, that means we
+ // should ensure the summary is no longer alerting, so we remove it here.
+ handlePostedEntry(summaryUpdateForRemoval, "detached-summary-remove-alert")
+ } else if (summaryUpdate!=null) {
+ mLogger.logPostedEntryWillNotEvaluate(summaryUpdate, "attached-summary-transferred")
+ }
+
+ // Handle all posted entries -- if the child receiving the parent's alert is in the
+ // list, then set its flags to ensure it alerts.
+ var didAlertChildToReceiveParentAlert = false
+ postedEntries.asSequence()
+ .filter { it.key != logicalSummary.key }
+ .forEach { postedEntry ->
+ if (childToReceiveParentAlert.key == postedEntry.key) {
+ // Update the child's posted update so that it
+ postedEntry.shouldHeadsUpEver = true
+ postedEntry.shouldHeadsUpAgain = true
+ handlePostedEntry(postedEntry, "child-alert-transfer-target-$targetType")
+ didAlertChildToReceiveParentAlert = true
+ } else {
+ handlePostedEntry(postedEntry, "child-alert-non-target")
+ }
+ }
+
+ // If the child receiving the alert was not updated on this tick (which can happen in a
+ // standard alert transfer scenario), then construct an update so that we can apply it.
+ if (!didAlertChildToReceiveParentAlert) {
+ val posted = PostedEntry(
+ childToReceiveParentAlert,
+ wasAdded = false,
+ wasUpdated = false,
+ shouldHeadsUpEver = true,
+ shouldHeadsUpAgain = true,
+ isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+ isBinding = isEntryBinding(childToReceiveParentAlert),
+ )
+ handlePostedEntry(posted, "non-posted-child-alert-transfer-target-$targetType")
+ }
+ }
+ // After this method runs, all posted entries should have been handled (or skipped).
+ mPostedEntries.clear()
+ }
+
+ /**
+ * Find the posted child with the newest when, and return it if it is isolated and has
+ * GROUP_ALERT_SUMMARY so that it can be alerted.
+ */
+ private fun findAlertOverride(
+ postedEntries: List<PostedEntry>,
+ locationLookupByKey: (String) -> GroupLocation,
+ ): NotificationEntry? = postedEntries.asSequence()
+ .filter { posted -> !posted.entry.sbn.notification.isGroupSummary }
+ .sortedBy { posted -> -posted.entry.sbn.notification.`when` }
+ .firstOrNull()
+ ?.let { posted ->
+ posted.entry.takeIf { entry ->
+ locationLookupByKey(entry.key) == GroupLocation.Isolated
+ && entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY
+ }
+ }
+
+ /**
+ * Of children which are attached, look for the child to receive the notification:
+ * First prefer children which were updated, then looking for the ones with the newest 'when'
+ */
+ private fun findBestTransferChild(
+ logicalMembers: List<NotificationEntry>,
+ locationLookupByKey: (String) -> GroupLocation,
+ ): NotificationEntry? = logicalMembers.asSequence()
+ .filter { !it.sbn.notification.isGroupSummary }
+ .filter { locationLookupByKey(it.key) != GroupLocation.Detached }
+ .sortedWith(compareBy(
+ { !mPostedEntries.contains(it.key) },
+ { -it.sbn.notification.`when` },
+ ))
+ .firstOrNull()
+
+ private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> =
+ mutableMapOf<String, GroupLocation>().also { map ->
+ list.forEach { topLevelEntry ->
+ when (topLevelEntry) {
+ is NotificationEntry -> map[topLevelEntry.key] = GroupLocation.Isolated
+ is GroupEntry -> {
+ topLevelEntry.summary?.let { summary ->
+ map[summary.key] = GroupLocation.Summary
+ }
+ topLevelEntry.children.forEach { child ->
+ map[child.key] = GroupLocation.Child
+ }
+ }
+ else -> error("unhandled type $topLevelEntry")
+ }
+ }
+ }
+
+ private val mPostedEntries = LinkedHashMap<String, PostedEntry>()
+
+ fun handlePostedEntry(posted: PostedEntry, scenario: String) {
+ mLogger.logPostedEntryWillEvaluate(posted, scenario)
+ if (posted.wasAdded) {
+ if (posted.shouldHeadsUpEver) {
+ bindForAsyncHeadsUp(posted)
+ }
+ } else {
+ if (posted.isHeadsUpAlready) {
+ // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR
+ // it could be because we're binding, and that will affect the next step.
+ if (posted.shouldHeadsUpEver) {
+ // If alerting, we need to post an update. Otherwise we're still binding,
+ // and we can just let that finish.
+ if (posted.isAlerting) {
+ mHeadsUpManager.updateNotification(posted.key, posted.shouldHeadsUpAgain)
+ }
+ } else {
+ if (posted.isAlerting) {
+ // We don't want this to be interrupting anymore, let's remove it
+ mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/)
+ } else {
+ // Don't let the bind finish
+ cancelHeadsUpBind(posted.entry)
+ }
+ }
+ } else if (posted.shouldHeadsUpEver && posted.shouldHeadsUpAgain) {
+ // This notification was updated to be heads up, show it!
+ bindForAsyncHeadsUp(posted)
+ }
+ }
+ }
+
+ private fun cancelHeadsUpBind(entry: NotificationEntry) {
+ mEntriesBindingUntil.remove(entry.key)
+ mHeadsUpViewBinder.abortBindCallback(entry)
+ }
+
+ private fun bindForAsyncHeadsUp(posted: PostedEntry) {
+ // TODO: Add a guarantee to bindHeadsUpView of some kind of callback if the bind is
+ // cancelled so that we don't need to have this sad timeout hack.
+ mEntriesBindingUntil[posted.key] = mNow + BIND_TIMEOUT
+ mHeadsUpViewBinder.bindHeadsUpView(posted.entry, this::onHeadsUpViewBound)
}
private val mNotifCollectionListener = object : NotifCollectionListener {
@@ -82,9 +343,17 @@
* Notification was just added and if it should heads up, bind the view and then show it.
*/
override fun onEntryAdded(entry: NotificationEntry) {
- if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
- mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
- }
+ // shouldHeadsUp includes check for whether this notification should be filtered
+ val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+ mPostedEntries[entry.key] = PostedEntry(
+ entry,
+ wasAdded = true,
+ wasUpdated = false,
+ shouldHeadsUpEver = shouldHeadsUpEver,
+ shouldHeadsUpAgain = true,
+ isAlerting = false,
+ isBinding = false,
+ )
}
/**
@@ -93,22 +362,26 @@
* up again.
*/
override fun onEntryUpdated(entry: NotificationEntry) {
- val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification)
- // includes check for whether this notification should be filtered:
- val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
- val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key)
- if (wasHeadsUp) {
- if (shouldHeadsUp) {
- mHeadsUpManager.updateNotification(entry.key, hunAgain)
- } else {
- // We don't want this to be interrupting anymore, let's remove it
- mHeadsUpManager.removeNotification(
- entry.key, false /* removeImmediately */
- )
- }
- } else if (shouldHeadsUp && hunAgain) {
- // This notification was updated to be heads up, show it!
- mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
+ val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+ val shouldHeadsUpAgain = shouldHunAgain(entry)
+ val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+ val isBinding = isEntryBinding(entry)
+ mPostedEntries.compute(entry.key) { _, value ->
+ value?.also { update ->
+ update.wasUpdated = true
+ update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver
+ update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain
+ update.isAlerting = isAlerting
+ update.isBinding = isBinding
+ } ?: PostedEntry(
+ entry,
+ wasAdded = false,
+ wasUpdated = true,
+ shouldHeadsUpEver = shouldHeadsUpEver,
+ shouldHeadsUpAgain = shouldHeadsUpAgain,
+ isAlerting = isAlerting,
+ isBinding = isBinding,
+ )
}
}
@@ -116,8 +389,12 @@
* Stop alerting HUNs that are removed from the notification collection
*/
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ mPostedEntries.remove(entry.key)
+ cancelHeadsUpBind(entry)
val entryKey = entry.key
if (mHeadsUpManager.isAlerting(entryKey)) {
+ // TODO: This should probably know the RemoteInputCoordinator's conditions,
+ // or otherwise reference that coordinator's state, rather than replicate its logic
val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
!NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
@@ -129,6 +406,14 @@
}
}
+ /**
+ * Checks whether an update for a notification warrants an alert for the user.
+ */
+ private fun shouldHunAgain(entry: NotificationEntry): Boolean {
+ return (!entry.hasInterrupted() ||
+ (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
+ }
+
private val mLifetimeExtender = object : NotifLifetimeExtender {
override fun getName() = TAG
@@ -164,11 +449,20 @@
private val mNotifPromoter = object : NotifPromoter(TAG) {
override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean =
- isCurrentlyShowingHun(entry)
+ isGoingToShowHunNoRetract(entry)
}
val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
- override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry)
+ override fun isInSection(entry: ListEntry): Boolean =
+ // TODO: This check won't notice if a child of the group is going to HUN...
+ isGoingToShowHunNoRetract(entry)
+
+ override fun getComparator(): NotifComparator {
+ return object : NotifComparator("HeadsUp") {
+ override fun compare(o1: ListEntry, o2: ListEntry): Int =
+ mHeadsUpManager.compare(o1.representativeEntry, o2.representativeEntry)
+ }
+ }
override fun getHeaderNodeController(): NodeController? =
// TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
@@ -186,7 +480,34 @@
private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)
- private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key)
+ private fun isEntryBinding(entry: ListEntry): Boolean {
+ val bindingUntil = mEntriesBindingUntil[entry.key]
+ return bindingUntil != null && bindingUntil >= mNow
+ }
+
+ /**
+ * Whether the notification is already alerting or binding so that it can imminently alert
+ */
+ private fun isAttemptingToShowHun(entry: ListEntry) =
+ mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+
+ /**
+ * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
+ * has been updated so that it should alert this update. This method is permissive because it
+ * returns `true` even if the update would (in isolation of its group) cause the alert to be
+ * retracted. This is important for not retracting transferred group alerts.
+ */
+ private fun isGoingToShowHunNoRetract(entry: ListEntry) =
+ mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry)
+
+ /**
+ * If the notification has been updated, then whether it should HUN in isolation, otherwise
+ * defers to the already alerting/binding state of [isAttemptingToShowHun]. This method is
+ * strict because any update which would revoke the alert supersedes the current
+ * alerting/binding state.
+ */
+ private fun isGoingToShowHunStrict(entry: ListEntry) =
+ mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry)
private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
if (mNotifsExtendingLifetime.remove(entry)) {
@@ -196,5 +517,29 @@
companion object {
private const val TAG = "HeadsUpCoordinator"
+ private const val BIND_TIMEOUT = 1000L
}
-}
\ No newline at end of file
+
+ data class PostedEntry(
+ val entry: NotificationEntry,
+ val wasAdded: Boolean,
+ var wasUpdated: Boolean,
+ var shouldHeadsUpEver: Boolean,
+ var shouldHeadsUpAgain: Boolean,
+ var isAlerting: Boolean,
+ var isBinding: Boolean,
+ ) {
+ val key = entry.key
+ val isHeadsUpAlready: Boolean
+ get() = isAlerting || isBinding
+ val calculateShouldBeHeadsUpStrict: Boolean
+ get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready)
+ val calculateShouldBeHeadsUpNoRetract: Boolean
+ get() = isHeadsUpAlready || (shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain))
+ }
+}
+
+private enum class GroupLocation { Detached, Isolated, Summary, Child }
+
+private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation =
+ getOrDefault(key, GroupLocation.Detached)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
new file mode 100644
index 0000000..204a494
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.util.Log
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+private const val TAG = "HeadsUpCoordinator"
+
+class HeadsUpCoordinatorLogger constructor(
+ private val buffer: LogBuffer,
+ private val verbose: Boolean,
+) {
+ @Inject
+ constructor(@NotificationHeadsUpLog buffer: LogBuffer) :
+ this(buffer, Log.isLoggable(TAG, Log.VERBOSE))
+
+ fun logPostedEntryWillEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = posted.key
+ str2 = reason
+ bool1 = posted.shouldHeadsUpEver
+ bool2 = posted.shouldHeadsUpAgain
+ }, {
+ "will evaluate posted entry $str1:" +
+ " reason=$str2 shouldHeadsUpEver=$bool1 shouldHeadsUpAgain=$bool2"
+ })
+ }
+
+ fun logPostedEntryWillNotEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = posted.key
+ str2 = reason
+ }, {
+ "will not evaluate posted entry $str1: reason=$str2"
+ })
+ }
+
+ fun logEvaluatingGroups(numGroups: Int) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ int1 = numGroups
+ }, {
+ "evaluating groups for alert transfer: $int1"
+ })
+ }
+
+ fun logEvaluatingGroup(groupKey: String, numPostedEntries: Int, logicalGroupSize: Int) {
+ if (!verbose) return
+ buffer.log(TAG, LogLevel.VERBOSE, {
+ str1 = groupKey
+ int1 = numPostedEntries
+ int2 = logicalGroupSize
+ }, {
+ "evaluating group for alert transfer: $str1" +
+ " numPostedEntries=$int1 logicalGroupSize=$int2"
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 0ce07cb..22300d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -98,6 +98,8 @@
setupInvalidateNotifListCallbacks();
// Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
pipeline.addFinalizeFilter(mNotifFilter);
+
+ updateSectionHeadersVisibility();
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -164,6 +166,8 @@
}
}
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
private void setupInvalidateNotifListCallbacks() {
// register onKeyguardShowing callback
mKeyguardStateController.addCallback(mKeyguardCallback);
@@ -220,10 +224,7 @@
}
private void invalidateListFromFilter(String reason) {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+ updateSectionHeadersVisibility();
mNotifFilter.invalidateList();
}
@@ -235,6 +236,13 @@
1) == 0;
}
+ private void updateSectionHeadersVisibility() {
+ boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
+ boolean showSections = !onKeyguard && !neverShowSections;
+ mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
+ }
+
private final KeyguardStateController.Callback mKeyguardCallback =
new KeyguardStateController.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 850cb4b..757fb5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -20,7 +20,6 @@
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -64,7 +63,6 @@
private val mCoordinators: MutableList<Coordinator> = ArrayList()
private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()
- private val mOrderedComparators: MutableList<NotifComparator> = ArrayList()
/**
* Creates all the coordinators.
@@ -119,9 +117,6 @@
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
-
- // Manually add ordered comparators
- mOrderedComparators.add(conversationCoordinator.comparator)
}
/**
@@ -133,7 +128,6 @@
c.attach(pipeline)
}
pipeline.setSections(mOrderedSections)
- pipeline.setComparators(mOrderedComparators)
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index a115e04..9c82cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -17,7 +17,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,6 +29,7 @@
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Module
import dagger.Provides
@@ -36,9 +40,13 @@
@CoordinatorScope
fun provideCoordinator(
dynamicPrivacyController: DynamicPrivacyController,
- lockscreenUserManager: NotificationLockscreenUserManager
+ lockscreenUserManager: NotificationLockscreenUserManager,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ statusBarStateController: StatusBarStateController,
+ keyguardStateController: KeyguardStateController
): SensitiveContentCoordinator =
- SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+ SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager,
+ keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
}
/** Coordinates re-inflation and post-processing of sensitive notification content. */
@@ -46,7 +54,10 @@
private class SensitiveContentCoordinatorImpl(
private val dynamicPrivacyController: DynamicPrivacyController,
- private val lockscreenUserManager: NotificationLockscreenUserManager
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardStateController: KeyguardStateController
) : Invalidator("SensitiveContentInvalidator"),
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
@@ -61,6 +72,19 @@
override fun onDynamicPrivacyChanged(): Unit = invalidateList()
override fun onBeforeRenderList(entries: List<ListEntry>) {
+ if (keyguardStateController.isKeyguardGoingAway() ||
+ statusBarStateController.getState() == StatusBarState.KEYGUARD &&
+ keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ // don't update yet if:
+ // - the keyguard is currently going away
+ // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
+
+ // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the
+ // dependent state changes invalidate the pipeline
+ return
+ }
+
val currentUserId = lockscreenUserManager.currentUserId
val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
val deviceSensitive = devicePublic &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index c6a8a69..1c96e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
@@ -38,10 +39,11 @@
pipeline.addOnAfterRenderListListener(::onAfterRenderList)
}
- fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) {
- controller.setNotifStats(calculateNotifStats(entries))
- notificationIconAreaController.updateNotificationIcons(entries)
- }
+ fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
+ traceSection("StackCoordinator.onAfterRenderList") {
+ controller.setNotifStats(calculateNotifStats(entries))
+ notificationIconAreaController.updateNotificationIcons(entries)
+ }
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
var hasNonClearableAlertingNotifs = false
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/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index 8444287..263737e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.listbuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.stack.PriorityBucket
@@ -29,5 +30,7 @@
val headerController: NodeController? = sectioner.headerNodeController
+ val comparator: NotifComparator? = sectioner.comparator
+
@PriorityBucket val bucket: Int = sectioner.bucket
}
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/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
index ef9ee11..8c52c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
@@ -55,8 +55,22 @@
public abstract boolean isInSection(ListEntry entry);
/**
+ * Returns an optional {@link NotifComparator} to sort entries only in this section.
+ * {@link ListEntry} instances passed to this comparator are guaranteed to have this section,
+ * and this ordering will take precedence over any global comparators supplied to {@link
+ * com.android.systemui.statusbar.notification.collection.NotifPipeline#setComparators(List)}.
+ *
+ * NOTE: this method is only called once when the Sectioner is attached.
+ */
+ public @Nullable NotifComparator getComparator() {
+ return null;
+ }
+
+ /**
* Returns an optional {@link NodeSpec} for the section header. If {@code null}, no header will
* be used for the section.
+ *
+ * NOTE: this method is only called once when the Sectioner is attached.
*/
public @Nullable NodeController getHeaderNodeController() {
return null;
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/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 4de8e7a..b76169f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.render
import android.view.View
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -41,6 +42,7 @@
override fun addChildAt(child: NodeController, index: Int) {
listContainer.addContainerViewAt(child.view, index)
listContainer.onNotificationViewUpdateFinished()
+ (child.view as? ExpandableNotificationRow)?.isChangingPosition = false
}
override fun moveChildTo(child: NodeController, index: Int) {
@@ -50,6 +52,7 @@
override fun removeChild(child: NodeController, isTransfer: Boolean) {
if (isTransfer) {
listContainer.setChildTransferInProgress(true)
+ (child.view as? ExpandableNotificationRow)?.isChangingPosition = true
}
listContainer.removeContainerView(child.view)
if (isTransfer) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 28cd285..386e2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -104,19 +104,14 @@
views.remove(childNode.controller.view)
}
- if (childCompletelyRemoved && parentSpec == null) {
- // If both the child and the parent are being removed at the same time, then
- // keep the child attached to the parent for animation purposes
- logger.logSkippingDetach(childNode.label, parentNode.label)
- } else {
- logger.logDetachingChild(
- childNode.label,
- !childCompletelyRemoved,
- parentNode.label,
- newParentNode?.label)
- parentNode.removeChild(childNode, !childCompletelyRemoved)
- childNode.parent = null
- }
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = parentSpec == null,
+ oldParent = parentNode.label,
+ newParent = newParentNode?.label)
+ parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+ childNode.parent = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index d274550..4c03572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -28,25 +28,18 @@
fun logDetachingChild(
key: String,
isTransfer: Boolean,
+ isParentRemoved: Boolean,
oldParent: String?,
newParent: String?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
bool1 = isTransfer
+ bool2 = isParentRemoved
str2 = oldParent
str3 = newParent
}, {
- "Detach $str1 isTransfer=$bool1 oldParent=$str2 newParent=$str3"
- })
- }
-
- fun logSkippingDetach(key: String, parent: String?) {
- buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
- str2 = parent
- }, {
- "Skipping detach of $str1 because its parent $str2 is also being detached"
+ "Detach $str1 isTransfer=$bool1 isParentRemoved=$bool2 oldParent=$str2 newParent=$str3"
})
}
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/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5d6d0f7..fca2aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -162,6 +162,13 @@
updateBackgroundTint();
}
+ /**
+ * @return The background of this view.
+ */
+ public NotificationBackgroundView getBackgroundNormal() {
+ return mBackgroundNormal;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index dbd22db..1f7d930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -49,6 +49,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -1246,6 +1247,7 @@
}
private void reInflateViews() {
+ Trace.beginSection("ExpandableNotificationRow#reInflateViews");
// Let's update our childrencontainer. This is intentionally not guarded with
// mIsSummaryWithChildren since we might have had children but not anymore.
if (mChildrenContainer != null) {
@@ -1277,6 +1279,7 @@
RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
params.setNeedsReinflation(true);
mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+ Trace.endSection();
}
@Override
@@ -1737,6 +1740,29 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
+
+ /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+ @NonNull
+ private String appendTraceStyleTag(@NonNull String traceTag) {
+ if (!Trace.isEnabled()) {
+ return traceTag;
+ }
+
+ Class<? extends Notification.Style> style =
+ getEntry().getSbn().getNotification().getNotificationStyle();
+ if (style == null) {
+ return traceTag + "(nostyle)";
+ } else {
+ return traceTag + "(" + style.getSimpleName() + ")";
+ }
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mPublicLayout = findViewById(R.id.expandedPublic);
@@ -2542,6 +2568,7 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout"));
int intrinsicBefore = getIntrinsicHeight();
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
@@ -2555,6 +2582,7 @@
if (mLayoutListener != null) {
mLayoutListener.onLayout();
}
+ Trace.endSection();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 46efef6..2436ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -248,19 +248,25 @@
mView.addChildNotification((ExpandableNotificationRow) child.getView(), index);
mListContainer.notifyGroupChildAdded(childView);
+ childView.setChangingPosition(false);
}
@Override
public void moveChildTo(NodeController child, int index) {
ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+ childView.setChangingPosition(true);
mView.removeChildNotification(childView);
mView.addChildNotification(childView, index);
+ childView.setChangingPosition(false);
}
@Override
public void removeChild(NodeController child, boolean isTransfer) {
ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
+ if (isTransfer) {
+ childView.setChangingPosition(true);
+ }
mView.removeChildNotification(childView);
if (!isTransfer) {
mListContainer.notifyGroupChildRemoved(childView, mView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 0f615aa..c640ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -29,6 +29,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
/**
@@ -39,15 +40,17 @@
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
- private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
private int mBackgroundTop;
private boolean mBottomAmountClips = true;
+ private int mActualHeight = -1;
+ private int mActualWidth = -1;
private boolean mExpandAnimationRunning;
- private float mActualWidth;
+ private int mExpandAnimationWidth = -1;
+ private int mExpandAnimationHeight = -1;
private int mDrawableAlpha = 255;
private boolean mIsPressedAllowed;
@@ -59,11 +62,12 @@
@Override
protected void onDraw(Canvas canvas) {
- if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+ if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
|| mExpandAnimationRunning) {
canvas.save();
if (!mExpandAnimationRunning) {
- canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ canvas.clipRect(0, mClipTopAmount, getWidth(),
+ getActualHeight() - mClipBottomAmount);
}
draw(canvas, mBackground);
canvas.restore();
@@ -73,17 +77,23 @@
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
int top = mBackgroundTop;
- int bottom = mActualHeight;
+ int bottom = getActualHeight();
if (mBottomIsRounded
&& mBottomAmountClips
&& !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- int left = 0;
- int right = getWidth();
+ final boolean isRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int actualWidth = getActualWidth();
+
+ int left = isRtl ? width - actualWidth : 0;
+ int right = isRtl ? width : actualWidth;
+
if (mExpandAnimationRunning) {
- left = (int) ((getWidth() - mActualWidth) / 2.0f);
- right = (int) (left + mActualWidth);
+ // Horizontally center this background view inside of the container
+ left = (int) ((width - actualWidth) / 2.0f);
+ right = (int) (left + actualWidth);
}
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
@@ -152,8 +162,26 @@
invalidate();
}
- public int getActualHeight() {
- return mActualHeight;
+ private int getActualHeight() {
+ if (mExpandAnimationRunning && mExpandAnimationHeight > -1) {
+ return mExpandAnimationHeight;
+ } else if (mActualHeight > -1) {
+ return mActualHeight;
+ }
+ return getHeight();
+ }
+
+ public void setActualWidth(int actualWidth) {
+ mActualWidth = actualWidth;
+ }
+
+ private int getActualWidth() {
+ if (mExpandAnimationRunning && mExpandAnimationWidth > -1) {
+ return mExpandAnimationWidth;
+ } else if (mActualWidth > -1) {
+ return mActualWidth;
+ }
+ return getWidth();
}
public void setClipTopAmount(int clipTopAmount) {
@@ -241,9 +269,9 @@
}
/** Set the current expand animation size. */
- public void setExpandAnimationSize(int actualWidth, int actualHeight) {
- mActualHeight = actualHeight;
- mActualWidth = actualWidth;
+ public void setExpandAnimationSize(int width, int height) {
+ mExpandAnimationHeight = width;
+ mExpandAnimationWidth = height;
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7dc2e19..ce3e27c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -565,6 +565,10 @@
}
}
+ public float getDozeAmount() {
+ return mDozeAmount;
+ }
+
/**
* Is the device fully awake, which is different from not tark at all when there are pulsing
* notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
deleted file mode 100644
index bd5b7d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack;
-
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-
-/**
- * Root view to insert Lock screen media controls into the notification stack.
- */
-public class MediaContainerView extends ExpandableView {
-
- public MediaContainerView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public long performRemoveAnimation(long duration, long delay, float translationDirection,
- boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
- return 0;
- }
-
- @Override
- public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
- Runnable onEnd) {
- // No animation, it doesn't need it, this would be local
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
new file mode 100644
index 0000000..b8f28b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -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.systemui.statusbar.notification.stack
+
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.RectF
+import android.util.AttributeSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.row.ExpandableView
+
+/**
+ * Root view to insert Lock screen media controls into the notification stack.
+ */
+class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
+
+ var cornerRadius = 0f
+ var clipHeight = 0
+ var clipRect = RectF()
+ var clipPath = Path()
+
+ init {
+ setWillNotDraw(false) // Run onDraw after invalidate.
+ updateResources()
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ private fun updateResources() {
+ cornerRadius = context.resources
+ .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+ }
+
+ public override fun updateClipping() {
+ if (clipHeight != actualHeight) {
+ clipHeight = actualHeight
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ val bounds = canvas.clipBounds
+ bounds.bottom = clipHeight
+ clipRect.set(bounds)
+
+ clipPath.reset()
+ clipPath.addRoundRect(clipRect, cornerRadius, cornerRadius, Path.Direction.CW)
+ canvas.clipPath(clipPath)
+ }
+
+
+ override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
+ isHeadsUpAnimation: Boolean, endLocation: Float,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?): Long {
+ return 0
+ }
+
+ override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean,
+ onEnd: Runnable?) {
+ // No animation, it doesn't need it, this would be local
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index b02dc0c..54e26c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -39,6 +39,7 @@
import com.android.systemui.util.children
import com.android.systemui.util.foldToSparseArray
import com.android.systemui.util.takeUntil
+import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
@@ -157,7 +158,9 @@
}
}
}
- private fun logShadeContents() = parent.children.forEachIndexed(::logShadeChild)
+ private fun logShadeContents() = traceSection("NotifSectionsManager.logShadeContents") {
+ parent.children.forEachIndexed(::logShadeChild)
+ }
private val isUsingMultipleSections: Boolean
get() = sectionsFeatureManager.getNumberOfBuckets() > 1
@@ -221,10 +224,10 @@
* Should be called whenever notifs are added, removed, or updated. Updates section boundary
* bookkeeping and adds/moves/removes section headers if appropriate.
*/
- fun updateSectionBoundaries(reason: String) {
+ fun updateSectionBoundaries(reason: String) = traceSection("NotifSectionsManager.update") {
notifPipelineFlags.checkLegacyPipelineEnabled()
if (!isUsingMultipleSections) {
- return
+ return@traceSection
}
logger.logStartSectionUpdate(reason)
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..25b8a65 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
@@ -43,7 +43,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
@@ -199,9 +198,10 @@
private int mMaxTopPadding;
private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
- private int mBottomMargin;
+ private int mBottomPadding;
private int mBottomInset = 0;
private float mQsExpansionFraction;
+ private final int mSplitShadeMinContentHeight;
/**
* The algorithm which calculates the properties for our children
@@ -305,7 +305,7 @@
return true;
}
};
-
+ private NotificationStackScrollLogger mLogger;
private StatusBar mStatusBar;
private int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
@@ -583,6 +583,8 @@
.getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
+ mSplitShadeMinContentHeight = res.getDimensionPixelSize(
+ R.dimen.nssl_split_shade_min_content_height);
mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
minHeight, maxHeight);
mExpandHelper.setEventSource(this);
@@ -658,6 +660,10 @@
return 0f;
}
+ protected void setLogger(NotificationStackScrollLogger logger) {
+ mLogger = logger;
+ }
+
public float getNotificationSquishinessFraction() {
return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
}
@@ -689,8 +695,7 @@
&& mQsExpansionFraction != 1
&& !mScreenOffAnimationController.shouldHideNotificationsFooter()
&& !mIsRemoteInputActive;
- boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ boolean showHistory = mController.isHistoryEnabled();
updateFooterView(showFooterView, showDismissView, showHistory);
}
@@ -736,6 +741,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<>();
@@ -959,7 +979,7 @@
mMinTopOverScrollToEscape = res.getDimensionPixelSize(
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
- mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mBottomPadding = res.getDimensionPixelSize(R.dimen.notification_panel_padding_bottom);
mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal);
mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape);
@@ -1254,8 +1274,7 @@
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
- // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
- float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion()
- getCurrentOverScrollAmount(false /* top */);
final float fraction = mAmbientState.getExpansionFraction();
@@ -1265,15 +1284,31 @@
mOnStackYChanged.accept(listenerNeedsAnimation);
}
if (mQsExpansionFraction <= 0) {
- final float stackEndHeight = Math.max(0f,
- getHeight() - getEmptyBottomMargin() - mTopPadding);
- mAmbientState.setStackEndHeight(stackEndHeight);
- mAmbientState.setStackHeight(
- MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION,
- stackEndHeight, fraction));
+ final float endHeight = updateStackEndHeight(
+ getHeight(), getEmptyBottomMargin(), mTopPadding);
+ updateStackHeight(endHeight, fraction);
}
}
+ public float updateStackEndHeight(float height, float bottomMargin, float topPadding) {
+ final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
+ mAmbientState.setStackEndHeight(stackEndHeight);
+ return stackEndHeight;
+ }
+
+ public void updateStackHeight(float endHeight, float fraction) {
+ // During the (AOD<=>LS) transition where dozeAmount is changing,
+ // apply dozeAmount to stack height instead of expansionFraction
+ // to unfurl notifications on AOD=>LS wakeup (and furl up on LS=>AOD sleep)
+ final float dozeAmount = mAmbientState.getDozeAmount();
+ if (0f < dozeAmount && dozeAmount < 1f) {
+ fraction = 1f - dozeAmount;
+ }
+ mAmbientState.setStackHeight(
+ MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, fraction));
+ }
+
/**
* Add a listener when the StackY changes. The argument signifies whether an animation is
* needed.
@@ -2243,7 +2278,7 @@
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
- mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
+ mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding;
updateScrollability();
clampScrollPosition();
updateStackPosition();
@@ -3100,10 +3135,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 +3158,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 +3166,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 +3190,7 @@
+ " onBottom=" + onBottom
+ " row=" + row.getEntry().getKey());
}
+ logHunAnimationEventAdded(key, type);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -3886,7 +3926,17 @@
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
int getEmptyBottomMargin() {
- return Math.max(mMaxLayoutHeight - mContentHeight, 0);
+ int contentHeight;
+ if (mShouldUseSplitNotificationShade) {
+ // When in split shade and there are no notifications, the height can be too low, as
+ // it is based on notifications bottom, which is lower on split shade.
+ // Here we prefer to use at least a minimum height defined for split shade.
+ // Otherwise the expansion motion is too fast.
+ contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight);
+ } else {
+ contentHeight = mContentHeight;
+ }
+ return Math.max(mMaxLayoutHeight - contentHeight, 0);
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4699,6 +4749,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));
@@ -5189,11 +5241,10 @@
R.layout.status_bar_no_notifications, this, false);
view.setText(R.string.empty_shade_text);
view.setOnClickListener(v -> {
- final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
- Intent intent = showHistory ? new Intent(
- Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
- Settings.ACTION_NOTIFICATION_SETTINGS);
+ final boolean showHistory = mController.isHistoryEnabled();
+ Intent intent = showHistory
+ ? new Intent(Settings.ACTION_NOTIFICATION_HISTORY)
+ : new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
});
setEmptyShadeView(view);
@@ -5443,6 +5494,17 @@
}
/**
+ * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states.
+ * Once the lockscreen to shade transition completes and the shade is 100% open
+ * LockscreenShadeTransitionController resets fraction to 0
+ * where it remains until the next lockscreen-to-shade transition.
+ */
+ public void setFractionToShade(float fraction) {
+ mShelf.setFractionToShade(fraction);
+ requestChildrenUpdate();
+ }
+
+ /**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
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..a2929f0 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
@@ -35,6 +35,8 @@
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
+import android.os.Trace;
+import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -180,16 +182,17 @@
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: StatusBar should be encapsulated behind a Controller
private final StatusBar mStatusBar;
- private final NotificationGroupManagerLegacy mLegacyGroupManager;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackStateLogger;
+ private final NotificationStackScrollLogger mLogger;
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
private NotificationSwipeHelper mSwipeHelper;
private boolean mShowEmptyShadeView;
+ @Nullable private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
@@ -339,6 +342,8 @@
@Override
public void onUserChanged(int userId) {
mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ mHistoryEnabled = null;
+ updateFooter();
}
};
@@ -662,8 +667,10 @@
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
- StackStateLogger stackLogger) {
+ StackStateLogger stackLogger,
+ NotificationStackScrollLogger logger) {
mStackStateLogger = stackLogger;
+ mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
mVisibilityProvider = visibilityProvider;
@@ -696,8 +703,6 @@
}
});
mNotifPipelineFlags = notifPipelineFlags;
- mLegacyGroupManager = mNotifPipelineFlags.isNewPipelineEnabled()
- ? null : legacyGroupManager;
mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
@@ -717,6 +722,7 @@
mView = view;
mView.setLogger(mStackStateLogger);
mView.setController(this);
+ mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
mView.setStatusBar(mStatusBar);
mView.setClearAllAnimationListener(this::onAnimationEnd);
@@ -798,6 +804,7 @@
(key, newValue) -> {
switch (key) {
case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
+ mHistoryEnabled = null; // invalidate
updateFooter();
break;
case HIGH_PRIORITY:
@@ -1005,6 +1012,20 @@
return mNotifStats.getNumActiveNotifs();
}
+ public boolean isHistoryEnabled() {
+ Boolean historyEnabled = mHistoryEnabled;
+ if (historyEnabled == null) {
+ if (mView == null || mView.getContext() == null) {
+ Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
+ return false;
+ }
+ mHistoryEnabled = historyEnabled =
+ Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ }
+ return historyEnabled;
+ }
+
public int getIntrinsicContentHeight() {
return mView.getIntrinsicContentHeight();
}
@@ -1195,6 +1216,7 @@
* are true.
*/
public void updateShowEmptyShadeView() {
+ Trace.beginSection("NSSLC.updateShowEmptyShadeView");
mShowEmptyShadeView = mBarState != KEYGUARD
&& (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
&& getVisibleNotificationCount() == 0;
@@ -1202,6 +1224,7 @@
mView.updateEmptyShadeView(
mShowEmptyShadeView,
mZenModeController.areNotificationsHiddenInShade());
+ Trace.endSection();
}
public boolean areNotificationsHiddenInShade() {
@@ -1319,11 +1342,15 @@
if (mNotifPipelineFlags.isNewPipelineEnabled()) {
return;
}
+ Trace.beginSection("NSSLC.updateSectionBoundaries");
mView.updateSectionBoundaries(reason);
+ Trace.endSection();
}
public void updateFooter() {
+ Trace.beginSection("NSSLC.updateFooter");
mView.updateFooter();
+ Trace.endSection();
}
public void onUpdateRowStates() {
@@ -1511,10 +1538,18 @@
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
+ * @param amount The amount of pixels we have currently dragged down
+ * for the lockscreen to shade transition. 0f for all other states.
+ * @param fraction The fraction of lockscreen to shade transition.
+ * 0f for all other states.
+ *
+ * Once the lockscreen to shade transition completes and the shade is 100% open,
+ * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
+ * until the next lockscreen-to-shade transition.
*/
- public void setTransitionToFullShadeAmount(float amount) {
+ public void setTransitionToFullShadeAmount(float amount, float fraction) {
+ mView.setFractionToShade(fraction);
+
float extraTopInset = 0.0f;
if (mStatusBarStateController.getState() == KEYGUARD) {
float overallProgress = MathUtils.saturate(amount / mView.getHeight());
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/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8f0579c..e24cd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -46,7 +46,7 @@
*/
public class StackScrollAlgorithm {
- public static final float START_FRACTION = 0.3f;
+ public static final float START_FRACTION = 0.5f;
private static final String LOG_TAG = "StackScrollAlgorithm";
private final ViewGroup mHostView;
@@ -61,7 +61,7 @@
@VisibleForTesting float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
- private int mCloseHandleUnderlapHeight;
+ private int mMarginBottom;
public StackScrollAlgorithm(
Context context,
@@ -87,7 +87,7 @@
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
- mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap);
+ mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
}
/**
@@ -463,7 +463,7 @@
}
} else {
if (view instanceof EmptyShadeView) {
- float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight
+ float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom
- ambientState.getStackY();
viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
} else if (view != ambientState.getTrackedHeadsUpRow()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 0d2bddc..7c9df42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -44,6 +44,7 @@
public static final int ANIMATION_DURATION_STANDARD = 360;
public static final int ANIMATION_DURATION_CORNER_RADIUS = 200;
public static final int ANIMATION_DURATION_WAKEUP = 500;
+ public static final int ANIMATION_DURATION_WAKEUP_SCRIM = 667;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_SWIPE = 200;
@@ -343,9 +344,11 @@
for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
final ExpandableView changingView = (ExpandableView) event.mChangingView;
boolean loggable = false;
+ boolean isHeadsUp = false;
String key = null;
if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
loggable = true;
+ isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
}
if (event.animationType ==
@@ -357,6 +360,9 @@
// The position for this child was never generated, let's continue.
continue;
}
+ if (loggable && isHeadsUp) {
+ mLogger.logHUNViewAppearingWithAddEvent(key);
+ }
viewState.applyToView(changingView);
mNewAddChildren.add(changingView);
@@ -398,9 +404,18 @@
translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
}
+ Runnable postAnimation = changingView::removeFromTransientContainer;
+ if (loggable && isHeadsUp) {
+ mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+ String finalKey = key;
+ postAnimation = () -> {
+ mLogger.disappearAnimationEnded(finalKey);
+ changingView.removeFromTransientContainer();
+ };
+ }
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- 0, changingView::removeFromTransientContainer, null);
+ 0, postAnimation, null);
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -430,8 +445,7 @@
// this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
// ADD animations, which would not be logged here.
if (loggable) {
- mLogger.logHUNViewAppearing(
- ((ExpandableNotificationRow) changingView).getEntry().getKey());
+ mLogger.logHUNViewAppearing(key);
}
mTmpState.applyToView(changingView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 4315265..77377af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -24,6 +24,22 @@
})
}
+ fun logHUNViewDisappearingWithRemoveEvent(key: String) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = key
+ }, {
+ "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
+ })
+ }
+
+ fun logHUNViewAppearingWithAddEvent(key: String) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = key
+ }, {
+ "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
+ })
+ }
+
fun disappearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8d500fa..6a78370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
@@ -28,7 +30,10 @@
import android.os.Trace;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -48,6 +53,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -156,6 +162,7 @@
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final SessionTracker mSessionTracker;
private final Context mContext;
private final int mWakeUpDelay;
private int mMode;
@@ -173,6 +180,7 @@
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
+ private final LatencyTracker mLatencyTracker;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -273,7 +281,9 @@
ScreenLifecycle screenLifecycle,
AuthController authController,
StatusBarStateController statusBarStateController,
- KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+ KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ SessionTracker sessionTracker,
+ LatencyTracker latencyTracker) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -281,6 +291,7 @@
mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = notificationMediaManager;
+ mLatencyTracker = latencyTracker;
wakefulnessLifecycle.addObserver(mWakefulnessObserver);
screenLifecycle.addObserver(mScreenObserver);
@@ -297,6 +308,7 @@
mAuthController = authController;
mStatusBarStateController = statusBarStateController;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+ mSessionTracker = sessionTracker;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -334,13 +346,13 @@
public void onBiometricAcquired(BiometricSourceType biometricSourceType) {
Trace.beginSection("BiometricUnlockController#onBiometricAcquired");
releaseBiometricWakeLock();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (LatencyTracker.isEnabled(mContext)) {
+ if (isWakeAndUnlock()) {
+ if (mLatencyTracker.isEnabled()) {
int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
if (biometricSourceType == BiometricSourceType.FACE) {
action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
}
- LatencyTracker.getInstance(mContext).onActionStart(action);
+ mLatencyTracker.onActionStart(action);
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
@@ -376,7 +388,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
boolean unlockAllowed =
mKeyguardStateController.isOccluded()
@@ -641,7 +653,15 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
+
+ if (mLatencyTracker.isEnabled()) {
+ int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK;
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK;
+ }
+ mLatencyTracker.onActionCancel(action);
+ }
if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
@@ -656,7 +676,7 @@
if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
startWakeAndUnlock(MODE_SHOW_BOUNCER);
- UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+ UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
mNumConsecutiveFpFailures = 0;
}
}
@@ -670,7 +690,7 @@
.setType(MetricsEvent.TYPE_ERROR).setSubtype(toSubtype(biometricSourceType))
.addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
// if we're on the shade and we're locked out, immediately show the bouncer
if (biometricSourceType == BiometricSourceType.FINGERPRINT
@@ -680,7 +700,7 @@
&& (mStatusBarStateController.getState() == StatusBarState.SHADE
|| mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
startWakeAndUnlock(MODE_SHOW_BOUNCER);
- UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+ UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
}
cleanup();
}
@@ -786,6 +806,9 @@
return mBiometricType;
}
+ private @Nullable InstanceId getSessionId() {
+ return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ }
/**
* Translates biometric source type for logging purpose.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 00b54e9..2ec5f25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
@@ -27,7 +26,6 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -47,7 +45,6 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -72,7 +69,6 @@
private StatusIconContainer mStatusIconContainer;
private boolean mKeyguardUserSwitcherEnabled;
- private final UserManager mUserManager;
private boolean mIsPrivacyDotEnabled;
private int mSystemIconsSwitcherHiddenExpandedMargin;
@@ -99,10 +95,10 @@
*/
private int mTopClipping;
private final Rect mClipRect = new Rect(0, 0, 0, 0);
+ private boolean mIsUserSwitcherEnabled;
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mUserManager = UserManager.get(getContext());
}
@Override
@@ -163,6 +159,10 @@
updateKeyguardStatusBarHeight();
}
+ public void setUserSwitcherEnabled(boolean enabled) {
+ mIsUserSwitcherEnabled = enabled;
+ }
+
private void updateKeyguardStatusBarHeight() {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext);
@@ -200,11 +200,7 @@
// If we have no keyguard switcher, the screen width is under 600dp. In this case,
// we only show the multi-user switch if it's enabled through UserManager as well as
// by the user.
- // TODO(b/138661450) Move IPC calls to background
- boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
- mContext.getResources().getBoolean(
- R.bool.qs_show_user_switcher_for_single_user)));
- if (isMultiUserEnabled) {
+ if (mIsUserSwitcherEnabled) {
mMultiUserAvatar.setVisibility(View.VISIBLE);
} else {
mMultiUserAvatar.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 8187163..ee97fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -23,8 +23,10 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
+import android.os.UserManager;
import android.util.MathUtils;
import android.view.View;
@@ -92,6 +94,7 @@
private final BiometricUnlockController mBiometricUnlockController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
+ private final UserManager mUserManager;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -105,6 +108,11 @@
mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateUserSwitcher();
+ }
};
private final SystemStatusAnimationCallback mAnimationCallback =
@@ -159,6 +167,13 @@
}
@Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ updateUserSwitcher();
+ }
+ }
+
+ @Override
public void onBiometricRunningStateChanged(
boolean running,
BiometricSourceType biometricSourceType) {
@@ -230,7 +245,8 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
BiometricUnlockController biometricUnlockController,
SysuiStatusBarStateController statusBarStateController,
- StatusBarContentInsetsProvider statusBarContentInsetsProvider
+ StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+ UserManager userManager
) {
super(view);
mCarrierTextController = carrierTextController;
@@ -248,6 +264,7 @@
mBiometricUnlockController = biometricUnlockController;
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
+ mUserManager = userManager;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -293,7 +310,7 @@
}
mView.setOnApplyWindowInsetsListener(
(view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
-
+ updateUserSwitcher();
onThemeChanged();
}
@@ -437,6 +454,14 @@
}
/**
+ * Updates visibility of the user switcher button based on {@link android.os.UserManager} state.
+ */
+ private void updateUserSwitcher() {
+ mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(getResources().getBoolean(
+ R.bool.qs_show_user_switcher_for_single_user)));
+ }
+
+ /**
* Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and
* whether heads up is visible.
*/
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/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c09c485..ebfed1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -135,7 +135,8 @@
}
}.setDuration(CONTENT_FADE_DURATION);
- private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5;
+ private static final int MAX_ICONS_ON_AOD = 3;
+ public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
public static final int MAX_STATIC_ICONS = 4;
private static final int MAX_DOTS = 1;
@@ -386,6 +387,19 @@
}
/**
+ * @return Width of shelf for the given number of icons and overflow dot
+ */
+ public int calculateWidthFor(int numMaxIcons) {
+ if (getChildCount() == 0) {
+ return 0;
+ }
+ return (int) (getActualPaddingStart()
+ + numMaxIcons * mIconSize
+ + mOverflowWidth
+ + getActualPaddingEnd());
+ }
+
+ /**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
* If this is not a whole number, the fraction means by how much the icon is appearing.
@@ -394,7 +408,7 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK :
+ int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = getMaxOverflowStart();
@@ -414,7 +428,7 @@
}
boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
&& iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean noOverflowAfter = i == childCount - 1;
+ boolean isLastChild = i == childCount - 1;
float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
: 1f;
@@ -423,10 +437,10 @@
: StatusBarIconView.STATE_ICON;
boolean isOverflowing =
- (translationX > (noOverflowAfter ? layoutEnd - mIconSize
+ (translationX > (isLastChild ? layoutEnd - mIconSize
: overflowStart - mIconSize));
if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i;
+ firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
mVisualOverflowStart = layoutEnd - mOverflowWidth;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
new file mode 100644
index 0000000..ff48755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.SysUIUnfoldScope
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import javax.inject.Inject
+
+@SysUIUnfoldScope
+class NotificationPanelUnfoldAnimationController
+@Inject
+constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+
+ private val translateAnimator by lazy {
+ UnfoldConstantTranslateAnimator(
+ viewsIdToTranslate =
+ setOf(
+ ViewIdToTranslate(R.id.quick_settings_panel, LEFT),
+ ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT),
+ ViewIdToTranslate(R.id.rightLayout, RIGHT),
+ ViewIdToTranslate(R.id.clock, LEFT),
+ ViewIdToTranslate(R.id.date, LEFT)),
+ progressProvider = progressProvider)
+ }
+
+ fun setup(root: ViewGroup) {
+ val translationMax =
+ context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+ translateAnimator.init(root, translationMax)
+ }
+}
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 e59c99a..3d3a1da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -20,6 +20,7 @@
import static android.view.View.GONE;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static androidx.constraintlayout.widget.ConstraintSet.START;
@@ -162,6 +163,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
@@ -180,6 +182,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;
@@ -203,6 +206,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;
@@ -224,7 +228,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;
@@ -410,6 +415,7 @@
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
private int mSplitShadeStatusBarHeight;
+ private int mSplitShadeNotificationsScrimMarginBottom;
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
@@ -665,6 +671,10 @@
private boolean mStatusViewCentered = true;
private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+ private Optional<NotificationPanelUnfoldAnimationController>
+ mNotificationPanelUnfoldAnimationController;
+
+ private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
@@ -741,6 +751,7 @@
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarWindowStateController statusBarWindowStateController,
+ NotificationShadeWindowController notificationShadeWindowController,
DozeLog dozeLog,
DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
LatencyTracker latencyTracker, PowerManager powerManager,
@@ -800,6 +811,7 @@
dozeLog,
keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController,
+ notificationShadeWindowController,
vibratorHelper,
statusBarKeyguardViewManager,
latencyTracker,
@@ -921,6 +933,8 @@
mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+ mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
+ SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
mCommunalSourceMonitorCallback = (source) -> {
mUiExecutor.execute(() -> setCommunalSource(source));
@@ -1056,6 +1070,8 @@
mTapAgainViewController.init();
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+ mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
+ controller.setup(mNotificationContainerParent));
}
@Override
@@ -1154,6 +1170,9 @@
public void updateResources() {
mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
+ mSplitShadeNotificationsScrimMarginBottom =
+ mResources.getDimensionPixelSize(
+ R.dimen.split_shade_notifications_scrim_margin_bottom);
int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
mShouldUseSplitNotificationShade =
@@ -1163,6 +1182,8 @@
mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
+ int notificationsBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.notification_panel_margin_bottom);
int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
@@ -1191,9 +1212,12 @@
constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
+ constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM,
+ notificationsBottomMargin);
constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
constraintSet.applyTo(mNotificationContainerParent);
mAmbientState.setStackTopMargin(topMargin);
+ mNotificationsQSContainerController.updateMargins();
mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
updateKeyguardStatusViewAlignment(/* animate= */false);
@@ -1311,6 +1335,7 @@
setKeyguardBottomAreaVisibility(mBarState, false);
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
+ mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
}
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
@@ -2440,9 +2465,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();
@@ -2470,7 +2501,6 @@
mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mSplitShadeHeaderController.setShadeExpanded(mQsVisible);
- mKeyguardStatusBarViewController.updateViewState();
if (mCommunalViewController != null) {
mCommunalViewController.updateQsExpansion(qsExpansionFraction);
@@ -2545,7 +2575,8 @@
right = getView().getRight() + mDisplayRightInset;
} else {
top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight);
- bottom = top + mNotificationStackScrollLayoutController.getHeight();
+ bottom = top + mNotificationStackScrollLayoutController.getHeight()
+ - mSplitShadeNotificationsScrimMarginBottom;
left = mNotificationStackScrollLayoutController.getLeft();
right = mNotificationStackScrollLayoutController.getRight();
}
@@ -3426,6 +3457,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;
}
@@ -4153,9 +4206,10 @@
return false;
}
- // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
- // to pull down QS or expand the shade.
- if (mStatusBar.isBouncerShowingScrimmed()) {
+ // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // otherwise user would be able to pull down QS or expand the shade.
+ if (mStatusBar.isBouncerShowingScrimmed()
+ || mStatusBar.isBouncerShowingOverDream()) {
return false;
}
@@ -4324,6 +4378,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/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index c859e70..474653b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -111,6 +111,12 @@
private final SysuiColorExtractor mColorExtractor;
private final ScreenOffAnimationController mScreenOffAnimationController;
private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
+ /**
+ * Layout params would be aggregated and dispatched all at once if this is > 0.
+ *
+ * @see #batchApplyWindowLayoutParams(Runnable)
+ */
+ private int mDeferWindowLayoutParams;
@Inject
public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
@@ -437,6 +443,20 @@
}
}
+ private void applyWindowLayoutParams() {
+ if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+ }
+ }
+
+ @Override
+ public void batchApplyWindowLayoutParams(Runnable scope) {
+ mDeferWindowLayoutParams++;
+ scope.run();
+ mDeferWindowLayoutParams--;
+ applyWindowLayoutParams();
+ }
+
private void apply(State state) {
applyKeyguardFlags(state);
applyFocusableFlag(state);
@@ -451,9 +471,8 @@
applyHasTopUi(state);
applyNotTouchable(state);
applyStatusBarColorSpaceAgnosticFlag(state);
- if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
- mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
- }
+ applyWindowLayoutParams();
+
if (mHasTopUi != mHasTopUiChanged) {
whitelistIpcs(() -> {
try {
@@ -739,6 +758,7 @@
pw.println(TAG + ":");
pw.println(" mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
pw.println(" mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
+ pw.println(" mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
pw.println(mCurrentState);
if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
mNotificationShadeView.getViewRootImpl().dump(" ", pw);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index 34bb6d3..b457ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -1,6 +1,8 @@
package com.android.systemui.statusbar.phone
import android.view.WindowInsets
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
@@ -14,7 +16,8 @@
class NotificationsQSContainerController @Inject constructor(
view: NotificationsQuickSettingsContainer,
private val navigationModeController: NavigationModeController,
- private val overviewProxyService: OverviewProxyService
+ private val overviewProxyService: OverviewProxyService,
+ private val featureFlags: FeatureFlags
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
var qsExpanded = false
@@ -63,7 +66,7 @@
}
public override fun onViewAttached() {
- notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+ updateMargins()
overviewProxyService.addCallback(taskbarVisibilityListener)
mView.setInsetsChangedListener(windowInsetsListener)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
@@ -75,6 +78,10 @@
mView.removeQSFragmentAttachedListener()
}
+ fun updateMargins() {
+ notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+ }
+
override fun setCustomizerAnimating(animating: Boolean) {
if (isQSCustomizerAnimating != animating) {
isQSCustomizerAnimating = animating
@@ -104,7 +111,11 @@
}
mView.setPadding(0, 0, 0, containerPadding)
mView.setNotificationsMarginBottom(notificationsMargin)
- mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+ if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+ mView.setQSContainerPaddingBottom(notificationsMargin)
+ } else {
+ mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+ }
}
private fun calculateBottomSpacing(): Pair<Int, Int> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 9210a8b..95e3b70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -45,7 +45,6 @@
private View mStackScroller;
private View mKeyguardStatusBar;
- private int mStackScrollerMargin;
private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
@@ -53,6 +52,7 @@
private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
private QS mQs;
private View mQSScrollView;
+ private View mQSContainer;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -63,7 +63,6 @@
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
mStackScroller = findViewById(R.id.notification_stack_scroller);
- mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
@@ -72,6 +71,7 @@
mQs = (QS) fragment;
mQSFragmentAttachedListener.accept(mQs);
mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
+ mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
}
@Override
@@ -91,13 +91,23 @@
mQSScrollView.getPaddingLeft(),
mQSScrollView.getPaddingTop(),
mQSScrollView.getPaddingRight(),
+ paddingBottom);
+ }
+ }
+
+ public void setQSContainerPaddingBottom(int paddingBottom) {
+ if (mQSContainer != null) {
+ mQSContainer.setPadding(
+ mQSContainer.getPaddingLeft(),
+ mQSContainer.getPaddingTop(),
+ mQSContainer.getPaddingRight(),
paddingBottom
);
}
}
public int getDefaultNotificationsMarginBottom() {
- return mStackScrollerMargin;
+ return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
}
public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
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 05ac2a3..c466a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -57,6 +57,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -72,6 +73,10 @@
public abstract class PanelViewController {
public static final boolean DEBUG = PanelView.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
private static final int NO_FIXED_DURATION = -1;
private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
@@ -180,6 +185,7 @@
private boolean mExpandLatencyTracking;
private final PanelView mView;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
protected final Resources mResources;
protected final KeyguardStateController mKeyguardStateController;
protected final SysuiStatusBarStateController mStatusBarStateController;
@@ -222,6 +228,7 @@
DozeLog dozeLog,
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
+ NotificationShadeWindowController notificationShadeWindowController,
VibratorHelper vibratorHelper,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
LatencyTracker latencyTracker,
@@ -263,15 +270,16 @@
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
+ mNotificationShadeWindowController = notificationShadeWindowController;
mFlingAnimationUtils = flingAnimationUtilsBuilder
.reset()
- .setMaxLengthSeconds(0.6f)
- .setSpeedUpFactor(0.6f)
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
.reset()
- .setMaxLengthSeconds(0.5f)
- .setSpeedUpFactor(0.6f)
+ .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
.build();
mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
.reset()
@@ -502,7 +510,7 @@
private void endClosing() {
if (mClosing) {
- mClosing = false;
+ setIsClosing(false);
onClosingFinished();
}
}
@@ -577,7 +585,7 @@
boolean expandBecauseOfFalsing) {
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
- mClosing = true;
+ setIsClosing(true);
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
@@ -760,34 +768,36 @@
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
- if (mExpandLatencyTracking && h != 0f) {
- DejankUtils.postAfterTraversal(
- () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
- mExpandLatencyTracking = false;
- }
- float maxPanelHeight = getMaxPanelHeight();
- if (mHeightAnimator == null) {
- if (mTracking) {
- float overExpansionPixels = Math.max(0, h - maxPanelHeight);
- setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(
+ () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
}
- mExpandedHeight = Math.min(h, maxPanelHeight);
- } else {
- mExpandedHeight = h;
- }
+ float maxPanelHeight = getMaxPanelHeight();
+ if (mHeightAnimator == null) {
+ if (mTracking) {
+ float overExpansionPixels = Math.max(0, h - maxPanelHeight);
+ setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+ }
+ mExpandedHeight = Math.min(h, maxPanelHeight);
+ } else {
+ mExpandedHeight = h;
+ }
- // If we are closing the panel and we are almost there due to a slow decelerating
- // interpolator, abort the animation.
- if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
- mExpandedHeight = 0f;
- if (mHeightAnimator != null) {
- mHeightAnimator.end();
+ // If we are closing the panel and we are almost there due to a slow decelerating
+ // interpolator, abort the animation.
+ if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+ mExpandedHeight = 0f;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.end();
+ }
}
- }
- mExpandedFraction = Math.min(1f,
- maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
- onHeightUpdated(mExpandedHeight);
- updatePanelExpansionAndVisibility();
+ mExpandedFraction = Math.min(1f,
+ maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ onHeightUpdated(mExpandedHeight);
+ updatePanelExpansionAndVisibility();
+ });
}
/**
@@ -864,7 +874,7 @@
notifyExpandingStarted();
// Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- mClosing = true;
+ setIsClosing(true);
if (delayed) {
mNextCollapseSpeedUpFactor = speedUpFactor;
mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1145,6 +1155,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/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 1cb19ab..d6bf5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -49,29 +49,29 @@
}
override fun onViewAttached() {
- moveFromCenterAnimationController?.let { animationController ->
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+ if (moveFromCenterAnimationController == null) return
- val viewsToAnimate = arrayOf(
- statusBarLeftSide,
- systemIconArea
- )
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
- mView.viewTreeObserver.addOnPreDrawListener(object :
- ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- animationController.onViewsReady(viewsToAnimate)
- mView.viewTreeObserver.removeOnPreDrawListener(this)
- return true
- }
- })
+ val viewsToAnimate = arrayOf(
+ statusBarLeftSide,
+ systemIconArea
+ )
- mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
- val widthChanged = right - left != oldRight - oldLeft
- if (widthChanged) {
- moveFromCenterAnimationController.onStatusBarWidthChanged()
- }
+ mView.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ moveFromCenterAnimationController.onViewsReady(viewsToAnimate)
+ mView.viewTreeObserver.removeOnPreDrawListener(this)
+ return true
+ }
+ })
+
+ mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+ val widthChanged = right - left != oldRight - oldLeft
+ if (widthChanged) {
+ moveFromCenterAnimationController.onStatusBarWidthChanged()
}
}
@@ -162,9 +162,7 @@
PhoneStatusBarViewController(
view,
progressProvider.getOrNull(),
- unfoldComponent.map {
- it.getStatusBarMoveFromCenterAnimationController()
- }.getOrNull(),
+ unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
touchEventHandler,
configurationController
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 091831f..ea61a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -113,14 +113,16 @@
* the animation ends
*/
fun shouldDelayKeyguardShow(): Boolean =
- animations.any { it.shouldPlayAnimation() }
+ animations.any { it.shouldDelayKeyguardShow() }
/**
* Return true while we want to ignore requests to show keyguard, we need to handle pending
* keyguard lock requests manually
+ *
+ * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock]
*/
fun isKeyguardShowDelayed(): Boolean =
- animations.any { it.isAnimationPlaying() }
+ animations.any { it.isKeyguardShowDelayed() }
/**
* Return true to ignore requests to hide keyguard
@@ -211,6 +213,8 @@
fun shouldAnimateInKeyguard(): Boolean = false
fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+ fun shouldDelayKeyguardShow(): Boolean = false
+ fun isKeyguardShowDelayed(): Boolean = false
fun isKeyguardHideDelayed(): Boolean = false
fun shouldHideScrimOnWakeUp(): Boolean = false
fun overrideNotificationsDozeAmount(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d2e1650..ef5f216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -62,7 +62,7 @@
public void prepare(ScrimState previousState) {
mBlankScreen = false;
if (previousState == ScrimState.AOD) {
- mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+ mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
if (mDisplayRequiresBlanking) {
// DisplayPowerManager will blank the screen, we'll just
// set our scrim to black in this frame to avoid flickering and
@@ -70,7 +70,7 @@
mBlankScreen = true;
}
} else if (previousState == ScrimState.KEYGUARD) {
- mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
+ mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM;
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
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 d3f2794..c8cc807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -150,6 +150,7 @@
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -338,6 +339,7 @@
}
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
@@ -781,7 +783,8 @@
ActivityLaunchAnimator activityLaunchAnimator,
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
- DeviceStateManager deviceStateManager) {
+ DeviceStateManager deviceStateManager,
+ DreamOverlayStateController dreamOverlayStateController) {
super(context);
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -869,6 +872,7 @@
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
+ mDreamOverlayStateController = dreamOverlayStateController;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1544,6 +1548,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 +1582,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() {
@@ -2920,14 +2935,6 @@
return updateIsKeyguard();
}
- /**
- * stop(tag)
- * @return True if StatusBar state is FULLSCREEN_USER_SWITCHER.
- */
- public boolean isFullScreenUserSwitcherState() {
- return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
- }
-
boolean updateIsKeyguard() {
return updateIsKeyguard(false /* forceStateChange */);
}
@@ -2972,6 +2979,7 @@
}
public void showKeyguardImpl() {
+ Trace.beginSection("StatusBar#showKeyguard");
mIsKeyguard = true;
// In case we're locking while a smartspace transition is in progress, reset it.
mKeyguardUnlockAnimationController.resetSmartspaceTransition();
@@ -2980,20 +2988,17 @@
onLaunchTransitionFadingEnded();
}
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
- if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
- mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
- } else if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
+ if (!mLockscreenShadeTransitionController.isWakingToShadeLocked()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
updatePanelExpansionForKeyguard();
+ Trace.endSection();
}
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
mShadeController.instantExpandNotificationsPanel();
- } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
- instantCollapseNotificationPanel();
}
}
@@ -3591,26 +3596,28 @@
public void onStartedWakingUp() {
String tag = "StatusBar#onStartedWakingUp";
DejankUtils.startDetectingBlockingIpcs(tag);
- mDeviceInteractive = true;
- mWakeUpCoordinator.setWakingUp(true);
- if (!mKeyguardBypassController.getBypassEnabled()) {
- mHeadsUpManager.releaseAllImmediately();
- }
- updateVisibleToUser();
- updateIsKeyguard();
- mDozeServiceHost.stopDozing();
- // This is intentionally below the stopDozing call above, since it avoids that we're
- // unnecessarily animating the wakeUp transition. Animations should only be enabled
- // once we fully woke up.
- updateRevealEffect(true /* wakingUp */);
- updateNotificationPanelTouchState();
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mDeviceInteractive = true;
+ mWakeUpCoordinator.setWakingUp(true);
+ if (!mKeyguardBypassController.getBypassEnabled()) {
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ updateVisibleToUser();
+ updateIsKeyguard();
+ mDozeServiceHost.stopDozing();
+ // This is intentionally below the stopDozing call above, since it avoids that we're
+ // unnecessarily animating the wakeUp transition. Animations should only be enabled
+ // once we fully woke up.
+ updateRevealEffect(true /* wakingUp */);
+ updateNotificationPanelTouchState();
- // If we are waking up during the screen off animation, we should undo making the
- // expanded visible (we did that so the LightRevealScrim would be visible).
- if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
- makeExpandedInvisible();
- }
+ // If we are waking up during the screen off animation, we should undo making the
+ // expanded visible (we did that so the LightRevealScrim would be visible).
+ if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
+ makeExpandedInvisible();
+ }
+ });
DejankUtils.stopDetectingBlockingIpcs(tag);
}
@@ -4131,6 +4138,10 @@
return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
}
+ public boolean isBouncerShowingOverDream() {
+ return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+ }
+
/**
* When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 316e682..d42a423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.BiometricSourceType;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.Trace;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -48,9 +49,9 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -111,6 +112,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final Lazy<ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -211,6 +213,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
+ private final LatencyTracker mLatencyTracker;
private KeyguardBypassController mBypassController;
@Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
@@ -235,6 +238,7 @@
SysuiStatusBarStateController sysuiStatusBarStateController,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DreamOverlayStateController dreamOverlayStateController,
NavigationModeController navigationModeController,
DockManager dockManager,
NotificationShadeWindowController notificationShadeWindowController,
@@ -242,13 +246,15 @@
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
- Lazy<ShadeController> shadeController) {
+ Lazy<ShadeController> shadeController,
+ LatencyTracker latencyTracker) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
mConfigurationController = configurationController;
mNavigationModeController = navigationModeController;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mDreamOverlayStateController = dreamOverlayStateController;
mKeyguardStateController = keyguardStateController;
mMediaManager = notificationMediaManager;
mKeyguardUpdateManager = keyguardUpdateMonitor;
@@ -257,6 +263,7 @@
mKeyguardBouncerFactory = keyguardBouncerFactory;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
+ mLatencyTracker = latencyTracker;
}
@Override
@@ -370,6 +377,7 @@
*/
@Override
public void show(Bundle options) {
+ Trace.beginSection("StatusBarKeyguardViewManager#show");
mShowing = true;
mNotificationShadeWindowController.setKeyguardShowing(true);
mKeyguardStateController.notifyKeyguardState(mShowing,
@@ -377,6 +385,7 @@
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
+ Trace.endSection();
}
/**
@@ -711,6 +720,7 @@
@Override
public void hide(long startTime, long fadeoutDuration) {
+ Trace.beginSection("StatusBarKeyguardViewManager#hide");
mShowing = false;
mKeyguardStateController.notifyKeyguardState(mShowing,
mKeyguardStateController.isOccluded());
@@ -810,6 +820,7 @@
}
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
+ Trace.endSection();
}
private boolean needsBypassFading() {
@@ -850,15 +861,11 @@
}
private void wakeAndUnlockDejank() {
- if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
- && LatencyTracker.isEnabled(mContext)) {
+ if (mBiometricUnlockController.isWakeAndUnlock() && mLatencyTracker.isEnabled()) {
BiometricSourceType type = mBiometricUnlockController.getBiometricType();
- DejankUtils.postAfterTraversal(() -> {
- LatencyTracker.getInstance(mContext).onActionEnd(
- type == BiometricSourceType.FACE
- ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
- : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
- });
+ mLatencyTracker.onActionEnd(type == BiometricSourceType.FACE
+ ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK
+ : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK);
}
}
@@ -1174,8 +1181,9 @@
}
public boolean bouncerNeedsScrimming() {
- return mOccluded || mBouncer.willDismissWithAction()
- || mStatusBar.isFullScreenUserSwitcherState()
+ // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
+ return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
+ || mBouncer.willDismissWithAction()
|| (mBouncer.isShowing() && mBouncer.isScrimmed())
|| mBouncer.isFullscreenBouncer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 6d033477..79c0984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -18,11 +18,13 @@
import android.view.View
import android.view.WindowManager
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaProvider
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
+import kotlin.math.max
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
@@ -31,8 +33,11 @@
) {
private val transitionListener = TransitionListener()
- private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
- viewCenterProvider = StatusBarViewsCenterProvider())
+ private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
+ windowManager,
+ viewCenterProvider = StatusBarViewsCenterProvider(),
+ alphaProvider = StatusBarIconsAlphaProvider()
+ )
fun onViewsReady(viewsToAnimate: Array<View>) {
moveFromCenterAnimator.updateDisplayProperties()
@@ -65,4 +70,15 @@
moveFromCenterAnimator.onTransitionProgress(1f)
}
}
+
+ private class StatusBarIconsAlphaProvider : AlphaProvider {
+ override fun getAlpha(progress: Float): Float {
+ return max(
+ 0f,
+ (progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
+ )
+ }
+ }
}
+
+private const val ICONS_START_APPEARING_PROGRESS = 0.75F
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index e3b4caa..d6fc0a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -87,11 +89,8 @@
this(context, theme, dismissOnDeviceLock, null);
}
- /**
- * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing.
- */
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
- SystemUIDialogManager dialogManager) {
+ @Nullable SystemUIDialogManager dialogManager) {
super(context, theme);
mContext = context;
@@ -148,7 +147,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getWidth() {
- return getDefaultDialogWidth(mContext);
+ return getDefaultDialogWidth(this);
}
/**
@@ -279,36 +278,53 @@
// We need to create the dialog first, otherwise the size will be overridden when it is
// created.
dialog.create();
- dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
- getDefaultDialogHeight());
+ dialog.getWindow().setLayout(getDefaultDialogWidth(dialog), getDefaultDialogHeight());
}
- private static int getDefaultDialogWidth(Context context) {
- boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
- if (!isOnTablet) {
- return ViewGroup.LayoutParams.MATCH_PARENT;
- }
-
+ private static int getDefaultDialogWidth(Dialog dialog) {
+ Context context = dialog.getContext();
int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
if (flagValue == -1) {
// The width of bottom sheets (624dp).
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, 624);
} else if (flagValue == -2) {
// The suggested small width for all dialogs (348dp)
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, 348);
} else if (flagValue > 0) {
// Any given width.
- return Math.round(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
- context.getResources().getDisplayMetrics()));
+ return calculateDialogWidthWithInsets(dialog, flagValue);
} else {
- // By default we use the same width as the notification shade in portrait mode (504dp).
- return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ // By default we use the same width as the notification shade in portrait mode.
+ int width = context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ if (width > 0) {
+ // If we are neither WRAP_CONTENT or MATCH_PARENT, add the background insets so that
+ // the dialog is the desired width.
+ width += getHorizontalInsets(dialog);
+ }
+ return width;
}
}
+ /**
+ * Return the pixel width {@param dialog} should be so that it is {@param widthInDp} wide,
+ * taking its background insets into consideration.
+ */
+ private static int calculateDialogWidthWithInsets(Dialog dialog, int widthInDp) {
+ float widthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthInDp,
+ dialog.getContext().getResources().getDisplayMetrics());
+ return Math.round(widthInPixels + getHorizontalInsets(dialog));
+ }
+
+ private static int getHorizontalInsets(Dialog dialog) {
+ if (dialog.getWindow().getDecorView() == null) {
+ return 0;
+ }
+
+ Drawable background = dialog.getWindow().getDecorView().getBackground();
+ Insets insets = background != null ? background.getOpticalInsets() : Insets.NONE;
+ return insets.left + insets.right;
+ }
+
private static int getDefaultDialogHeight() {
return ViewGroup.LayoutParams.WRAP_CONTENT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cc65ca02..0abe8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -136,6 +136,12 @@
globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
}
+ override fun shouldDelayKeyguardShow(): Boolean =
+ shouldPlayAnimation()
+
+ override fun isKeyguardShowDelayed(): Boolean =
+ isAnimationPlaying()
+
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
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/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index f5364b9..d3ff4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -40,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -231,7 +232,8 @@
ActivityLaunchAnimator activityLaunchAnimator,
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
- DeviceStateManager deviceStateManager) {
+ DeviceStateManager deviceStateManager,
+ DreamOverlayStateController dreamOverlayStateController) {
return new StatusBar(
context,
notificationsController,
@@ -327,7 +329,8 @@
activityLaunchAnimator,
notifPipelineFlags,
jankMonitor,
- deviceStateManager
+ deviceStateManager,
+ dreamOverlayStateController
);
}
}
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/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 3084a95..784a5468 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -350,11 +350,14 @@
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(@NonNull NotificationEntry a, @NonNull NotificationEntry b) {
+ public int compare(@Nullable NotificationEntry a, @Nullable NotificationEntry b) {
+ if (a == null || b == null) {
+ return Boolean.compare(a == null, b == null);
+ }
AlertEntry aEntry = getHeadsUpEntry(a.getKey());
AlertEntry bEntry = getHeadsUpEntry(b.getKey());
if (aEntry == null || bEntry == null) {
- return aEntry == null ? 1 : -1;
+ return Boolean.compare(aEntry == null, bEntry == null);
}
return aEntry.compareTo(bEntry);
}
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/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 48949f92..3205e09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -118,6 +118,7 @@
private boolean mColorized;
private int mTint;
private boolean mResetting;
+ private boolean mWasSpinning;
// TODO(b/193539698): move these to a Controller
private RemoteInputController mController;
@@ -439,6 +440,10 @@
mEditText.requestFocus();
}
}
+ if (mWasSpinning) {
+ mController.addSpinning(mEntry.getKey(), mToken);
+ mWasSpinning = false;
+ }
}
@Override
@@ -447,6 +452,7 @@
mEditText.removeTextChangedListener(mTextWatcher);
mEditText.setOnEditorActionListener(null);
mEditText.mRemoteInputView = null;
+ mWasSpinning = mController.isSpinning(mEntry.getKey(), mToken);
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
@@ -533,6 +539,8 @@
if (isActive() && mWrapper != null) {
mWrapper.setRemoteInputVisible(true);
}
+
+ mWasSpinning = false;
}
private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt
deleted file mode 100644
index c6dbdb1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy
-
-/**
- * Interface for tracking packages with running foreground services and demoting foreground status
- */
-interface RunningFgsController : CallbackController<RunningFgsController.Callback> {
-
- /**
- * @return A list of [UserPackageTime]s which have running foreground service(s)
- */
- fun getPackagesWithFgs(): List<UserPackageTime>
-
- /**
- * Stops all foreground services running as a package
- * @param userId the userId the package is running under
- * @param packageName the packageName
- */
- fun stopFgs(userId: Int, packageName: String)
-
- /**
- * Returns when the list of packages with foreground services changes
- */
- interface Callback {
- /**
- * The thing that
- * @param packages the list of packages
- */
- fun onFgsPackagesChanged(packages: List<UserPackageTime>)
- }
-
- /**
- * A triplet <user, packageName, timeMillis> where each element is a package running
- * under a user that has had at least one foreground service running since timeMillis.
- * Time should be derived from [SystemClock.elapsedRealtime].
- */
- data class UserPackageTime(val userId: Int, val packageName: String, val startTimeMillis: Long)
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt
deleted file mode 100644
index d44d365..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy
-
-import android.app.IActivityManager
-import android.app.IForegroundServiceObserver
-import android.os.IBinder
-import android.os.RemoteException
-import android.util.Log
-import androidx.annotation.GuardedBy
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.statusbar.policy.RunningFgsController.Callback
-import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime
-import com.android.systemui.util.time.SystemClock
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Implementation for [RunningFgsController]
- */
-@SysUISingleton
-class RunningFgsControllerImpl @Inject constructor(
- @Background private val executor: Executor,
- private val systemClock: SystemClock,
- private val activityManager: IActivityManager
-) : RunningFgsController, IForegroundServiceObserver.Stub() {
-
- companion object {
- private val LOG_TAG = RunningFgsControllerImpl::class.java.simpleName
- }
-
- private val lock = Any()
-
- @GuardedBy("lock")
- var initialized = false
-
- @GuardedBy("lock")
- private val runningServiceTokens = mutableMapOf<UserPackageKey, StartTimeAndTokensValue>()
-
- @GuardedBy("lock")
- private val callbacks = mutableSetOf<Callback>()
-
- fun init() {
- synchronized(lock) {
- if (initialized) {
- return
- }
- try {
- activityManager.registerForegroundServiceObserver(this)
- } catch (e: RemoteException) {
- e.rethrowFromSystemServer()
- }
-
- initialized = true
- }
- }
-
- override fun addCallback(listener: Callback) {
- init()
- synchronized(lock) { callbacks.add(listener) }
- }
-
- override fun removeCallback(listener: Callback) {
- init()
- synchronized(lock) {
- if (!callbacks.remove(listener)) {
- Log.e(LOG_TAG, "Callback was not registered.", RuntimeException())
- }
- }
- }
-
- override fun observe(lifecycle: Lifecycle?, listener: Callback?): Callback {
- init()
- return super.observe(lifecycle, listener)
- }
-
- override fun observe(owner: LifecycleOwner?, listener: Callback?): Callback {
- init()
- return super.observe(owner, listener)
- }
-
- override fun getPackagesWithFgs(): List<UserPackageTime> {
- init()
- return synchronized(lock) { getPackagesWithFgsLocked() }
- }
-
- private fun getPackagesWithFgsLocked(): List<UserPackageTime> =
- runningServiceTokens.map {
- UserPackageTime(it.key.userId, it.key.packageName, it.value.fgsStartTime)
- }
-
- override fun stopFgs(userId: Int, packageName: String) {
- init()
- try {
- activityManager.makeServicesNonForeground(packageName, userId)
- } catch (e: RemoteException) {
- e.rethrowFromSystemServer()
- }
- }
-
- private data class UserPackageKey(
- val userId: Int,
- val packageName: String
- )
-
- private class StartTimeAndTokensValue(systemClock: SystemClock) {
- val fgsStartTime = systemClock.elapsedRealtime()
- var tokens = mutableSetOf<IBinder>()
- fun addToken(token: IBinder): Boolean {
- return tokens.add(token)
- }
-
- fun removeToken(token: IBinder): Boolean {
- return tokens.remove(token)
- }
-
- val isEmpty: Boolean
- get() = tokens.isEmpty()
- }
-
- override fun onForegroundStateChanged(
- token: IBinder,
- packageName: String,
- userId: Int,
- isForeground: Boolean
- ) {
- val result = synchronized(lock) {
- val userPackageKey = UserPackageKey(userId, packageName)
- if (isForeground) {
- var addedNew = false
- runningServiceTokens.getOrPut(userPackageKey) {
- addedNew = true
- StartTimeAndTokensValue(systemClock)
- }.addToken(token)
- if (!addedNew) {
- return
- }
- } else {
- val startTimeAndTokensValue = runningServiceTokens[userPackageKey]
- if (startTimeAndTokensValue?.removeToken(token) == false) {
- Log.e(LOG_TAG,
- "Stopped foreground service was not known to be running.")
- return
- }
- if (!startTimeAndTokensValue!!.isEmpty) {
- return
- }
- runningServiceTokens.remove(userPackageKey)
- }
- getPackagesWithFgsLocked().toList()
- }
-
- callbacks.forEach { executor.execute { it.onFgsPackagesChanged(result) } }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9f20bc5..1b73595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -19,7 +19,6 @@
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -427,22 +426,6 @@
return mSimpleUserSwitcher;
}
- public boolean useFullscreenUserSwitcher() {
- // Use adb to override:
- // adb shell settings put system enable_fullscreen_user_switcher 0 # Turn it off.
- // adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on.
- // Restart SystemUI or adb reboot.
- final int DEFAULT = -1;
- final int overrideUseFullscreenUserSwitcher =
- whitelistIpcs(() -> Settings.System.getInt(mContext.getContentResolver(),
- "enable_fullscreen_user_switcher", DEFAULT));
- if (overrideUseFullscreenUserSwitcher != DEFAULT) {
- return overrideUseFullscreenUserSwitcher != 0;
- }
- // Otherwise default to the build setting.
- return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
- }
-
public void setResumeUserOnGuestLogout(boolean resume) {
mResumeUserOnGuestLogout = resume;
}
@@ -455,6 +438,13 @@
}
}
+ /**
+ * Returns whether the current user is a system user.
+ */
+ public boolean isSystemUser() {
+ return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
+ }
+
public void removeUserId(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
Log.w(TAG, "User " + userId + " could not removed.");
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/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index e59d2f23..d0fb91c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -18,6 +18,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.statusbar.tv.VpnStatusObserver;
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import dagger.Binds;
@@ -34,4 +35,9 @@
@IntoMap
@ClassKey(TvNotificationHandler.class)
CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui);
+
+ @Binds
+ @IntoMap
+ @ClassKey(VpnStatusObserver.class)
+ CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index c481fc9..2e627a8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -20,7 +20,6 @@
import android.os.PowerManager
import android.provider.Settings
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -28,7 +27,6 @@
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
-import dagger.Lazy
import javax.inject.Inject
/**
@@ -40,7 +38,6 @@
@Inject
constructor(
@Main private val handler: Handler,
- private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val globalSettings: GlobalSettings
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
@@ -57,7 +54,6 @@
statusBar.notificationPanelViewController.startFoldToAodAnimation {
// End action
isAnimationPlaying = false
- keyguardViewMediatorLazy.get().maybeHandlePendingLock()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 07f9c54..7350b37 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -18,6 +18,7 @@
import com.android.keyguard.KeyguardUnfoldTransition
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
@@ -85,6 +86,8 @@
fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
+ fun getNotificationPanelUnfoldAnimationController(): NotificationPanelUnfoldAnimationController
+
fun getFoldAodAnimationController(): FoldAodAnimationController
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
new file mode 100644
index 0000000..b506808
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.util.drawable
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedImageDrawable
+import android.graphics.drawable.AnimatedRotateDrawable
+import android.graphics.drawable.AnimatedStateListDrawable
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import androidx.annotation.Px
+import com.android.systemui.util.traceSection
+
+class DrawableSize {
+
+ companion object {
+
+ const val TAG = "SysUiDrawableSize"
+
+ /**
+ * Downscales passed Drawable to set maximum width and height. This will only
+ * be done for Drawables that can be downscaled non-destructively - e.g. animated
+ * and stateful drawables will no be downscaled.
+ *
+ * Downscaling will keep the aspect ratio.
+ * This method will not touch drawables that already fit into size specification.
+ *
+ * @param resources Resources on which to base the density of resized drawable.
+ * @param drawable Drawable to downscale.
+ * @param maxWidth Maximum width of the downscaled drawable.
+ * @param maxHeight Maximum height of the downscaled drawable.
+ *
+ * @return returns downscaled drawable if it's possible to downscale it or original if it's
+ * not.
+ */
+ @JvmStatic
+ fun downscaleToSize(
+ res: Resources,
+ drawable: Drawable,
+ @Px maxWidth: Int,
+ @Px maxHeight: Int
+ ): Drawable {
+ traceSection("DrawableSize#downscaleToSize") {
+ // Bitmap drawables can contain big bitmaps as their content while sneaking it past
+ // us using density scaling. Inspect inside the Bitmap drawables for actual bitmap
+ // size for those.
+ val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width
+ ?: drawable.intrinsicWidth
+ val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height
+ ?: drawable.intrinsicHeight
+
+ // Don't touch drawable if we can't resolve sizes for whatever reason.
+ if (originalWidth <= 0 || originalHeight <= 0) {
+ return drawable
+ }
+
+ // Do not touch drawables that are already within bounds.
+ if (originalWidth < maxWidth && originalHeight < maxHeight) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Not resizing $originalWidth x $originalHeight" + " " +
+ "to $maxWidth x $maxHeight")
+ }
+
+ return drawable
+ }
+
+ if (!isSimpleBitmap(drawable)) {
+ return drawable
+ }
+
+ val scaleWidth = maxWidth.toFloat() / originalWidth.toFloat()
+ val scaleHeight = maxHeight.toFloat() / originalHeight.toFloat()
+ val scale = minOf(scaleHeight, scaleWidth)
+
+ val width = (originalWidth * scale).toInt()
+ val height = (originalHeight * scale).toInt()
+
+ if (width <= 0 || height <= 0) {
+ Log.w(TAG, "Attempted to resize ${drawable.javaClass.simpleName} " +
+ "from $originalWidth x $originalHeight to invalid $width x $height.")
+ return drawable
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " +
+ "from $originalWidth x $originalHeight to $width x $height")
+ }
+
+ // We want to keep existing config if it's more efficient than 32-bit RGB.
+ val config = (drawable as? BitmapDrawable)?.bitmap?.config
+ ?: Bitmap.Config.ARGB_8888
+ val scaledDrawableBitmap = Bitmap.createBitmap(width, height, config)
+ val canvas = Canvas(scaledDrawableBitmap)
+
+ val originalBounds = drawable.bounds
+ drawable.setBounds(0, 0, width, height)
+ drawable.draw(canvas)
+ drawable.bounds = originalBounds
+
+ return BitmapDrawable(res, scaledDrawableBitmap)
+ }
+ }
+
+ private fun isSimpleBitmap(drawable: Drawable): Boolean {
+ return !(drawable.isStateful || isAnimated(drawable))
+ }
+
+ private fun isAnimated(drawable: Drawable): Boolean {
+ if (drawable is Animatable || drawable is Animatable2) {
+ return true
+ }
+
+ return drawable is AnimatedImageDrawable ||
+ drawable is AnimatedRotateDrawable ||
+ drawable is AnimatedStateListDrawable ||
+ drawable is AnimatedVectorDrawable
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
new file mode 100644
index 0000000..7687432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+/**
+ * The {@link Observer} interface specifies an entity which listeners
+ * can be informed of changes to the source, which will require updating. Note that this deals
+ * with changes to the source itself, not content which will be updated through the interface.
+ */
+public interface Observer {
+ /**
+ * Callback for receiving updates from the {@link Observer}.
+ */
+ interface Callback {
+ /**
+ * Invoked when the source has changed.
+ */
+ void onSourceChanged();
+ }
+
+ /**
+ * Adds a callback to receive future updates from the {@link Observer}.
+ */
+ void addCallback(Callback callback);
+
+ /**
+ * Removes a callback from receiving further updates.
+ * @param callback
+ */
+ void removeCallback(Callback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
new file mode 100644
index 0000000..2ee7b20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Log;
+
+import com.android.systemui.communal.CommunalSource;
+
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+
+/**
+ * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
+ * package. This can be used by {@link CommunalSource} clients to detect when a related package
+ * has changed and reloading is necessary.
+ */
+public class PackageObserver implements Observer {
+ private static final String TAG = "PackageObserver";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "package added receiver - onReceive");
+ }
+
+ final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
+ while (iter.hasNext()) {
+ final Callback callback = iter.next().get();
+ if (callback != null) {
+ callback.onSourceChanged();
+ } else {
+ iter.remove();
+ }
+ }
+ }
+ };
+
+ private final String mPackageName;
+ private final Context mContext;
+
+ @Inject
+ public PackageObserver(Context context, ComponentName component) {
+ mContext = context;
+ mPackageName = component.getPackageName();
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "addCallback:" + callback);
+ }
+ mCallbacks.add(new WeakReference<>(callback));
+
+ // Only register for listening to package additions on first callback.
+ if (mCallbacks.size() > 1) {
+ return;
+ }
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
+ // Note that we directly register the receiver here as data schemes are not supported by
+ // BroadcastDispatcher.
+ mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "removeCallback:" + callback);
+ }
+ final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
+
+ if (removed && mCallbacks.isEmpty()) {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
new file mode 100644
index 0000000..292c062
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
+
+import android.util.Log;
+
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
+ * {@link ObservableServiceConnection}.
+ * @param <T> The transformed connection type handled by the service.
+ */
+public class PersistentConnectionManager<T> {
+ private static final String TAG = "PersistentConnManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final SystemClock mSystemClock;
+ private final DelayableExecutor mMainExecutor;
+ private final int mBaseReconnectDelayMs;
+ private final int mMaxReconnectAttempts;
+ private final int mMinConnectionDuration;
+ private final Observer mObserver;
+
+ private int mReconnectAttempts = 0;
+ private Runnable mCurrentReconnectCancelable;
+
+ private final ObservableServiceConnection<T> mConnection;
+
+ private final Runnable mConnectRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mCurrentReconnectCancelable = null;
+ mConnection.bind();
+ }
+ };
+
+ private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
+
+ private final ObservableServiceConnection.Callback mConnectionCallback =
+ new ObservableServiceConnection.Callback() {
+ private long mStartTime;
+
+ @Override
+ public void onConnected(ObservableServiceConnection connection, Object proxy) {
+ mStartTime = mSystemClock.currentTimeMillis();
+ }
+
+ @Override
+ public void onDisconnected(ObservableServiceConnection connection, int reason) {
+ if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
+ initiateConnectionAttempt();
+ } else {
+ scheduleConnectionAttempt();
+ }
+ }
+ };
+
+ @Inject
+ public PersistentConnectionManager(
+ SystemClock clock,
+ DelayableExecutor mainExecutor,
+ @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
+ @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
+ @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
+ @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
+ @Named(OBSERVER) Observer observer) {
+ mSystemClock = clock;
+ mMainExecutor = mainExecutor;
+ mConnection = serviceConnection;
+ mObserver = observer;
+
+ mMaxReconnectAttempts = maxReconnectAttempts;
+ mBaseReconnectDelayMs = baseReconnectDelayMs;
+ mMinConnectionDuration = minConnectionDurationMs;
+ }
+
+ /**
+ * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
+ */
+ public void start() {
+ mConnection.addCallback(mConnectionCallback);
+ mObserver.addCallback(mObserverCallback);
+ initiateConnectionAttempt();
+ }
+
+ /**
+ * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
+ */
+ public void stop() {
+ mConnection.removeCallback(mConnectionCallback);
+ mObserver.removeCallback(mObserverCallback);
+ mConnection.unbind();
+ }
+
+ private void initiateConnectionAttempt() {
+ // Reset attempts
+ mReconnectAttempts = 0;
+
+ // The first attempt is always a direct invocation rather than delayed.
+ mConnection.bind();
+ }
+
+ private void scheduleConnectionAttempt() {
+ // always clear cancelable if present.
+ if (mCurrentReconnectCancelable != null) {
+ mCurrentReconnectCancelable.run();
+ mCurrentReconnectCancelable = null;
+ }
+
+ if (mReconnectAttempts >= mMaxReconnectAttempts) {
+ if (DEBUG) {
+ Log.d(TAG, "exceeded max connection attempts.");
+ }
+ return;
+ }
+
+ final long reconnectDelayMs =
+ (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+ if (DEBUG) {
+ Log.d(TAG,
+ "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
+ }
+
+ mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+ reconnectDelayMs);
+
+ mReconnectAttempts++;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
new file mode 100644
index 0000000..c62c957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.systemui.util.service.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module containing components and parameters for
+ * {@link com.android.systemui.util.service.ObservableServiceConnection}
+ * and {@link com.android.systemui.util.service.PersistentConnectionManager}.
+ */
+@Module(subcomponents = {
+ PackageObserverComponent.class,
+})
+public class ObservableServiceModule {
+ public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
+ public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
+ public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
+ public static final String SERVICE_CONNECTION = "service_connection";
+ public static final String OBSERVER = "observer";
+
+ @Provides
+ @Named(MAX_RECONNECT_ATTEMPTS)
+ static int providesMaxReconnectAttempts(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_communalSourceMaxReconnectAttempts);
+ }
+
+ @Provides
+ @Named(BASE_RECONNECT_DELAY_MS)
+ static int provideBaseReconnectDelayMs(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_communalSourceReconnectBaseDelay);
+ }
+
+ @Provides
+ @Named(MIN_CONNECTION_DURATION_MS)
+ static int providesMinConnectionDuration(@Main Resources resources) {
+ return resources.getInteger(
+ R.integer.config_connectionMinDuration);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
new file mode 100644
index 0000000..8ee39b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.service.dagger;
+
+import android.content.ComponentName;
+
+import com.android.systemui.util.service.PackageObserver;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Generates a scoped {@link PackageObserver}.
+ */
+@Subcomponent
+public interface PackageObserverComponent {
+ /**
+ * Generates a {@link PackageObserverComponent} instance.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ PackageObserverComponent create(@BindsInstance ComponentName component);
+ }
+
+ /**
+ * Creates a {@link PackageObserver}.
+ */
+ PackageObserver getPackageObserver();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 11725ef..57c7f11 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -33,7 +33,6 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IVolumeController;
-import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import android.media.VolumePolicy;
@@ -47,7 +46,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
@@ -68,6 +66,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.ThreadFactory;
@@ -78,7 +77,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
@@ -135,7 +133,7 @@
protected C mCallbacks = new C();
private final State mState = new State();
protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
- private final Optional<Vibrator> mVibrator;
+ private final VibratorHelper mVibrator;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
private boolean mShowVolumeDialog;
@@ -173,7 +171,7 @@
ThreadFactory theadFactory,
AudioManager audioManager,
NotificationManager notificationManager,
- Optional<Vibrator> optionalVibrator,
+ VibratorHelper vibrator,
IAudioService iAudioService,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
@@ -199,8 +197,8 @@
mBroadcastDispatcher = broadcastDispatcher;
mObserver.init();
mReceiver.init();
- mVibrator = optionalVibrator;
- mHasVibrator = mVibrator.isPresent() && mVibrator.get().hasVibrator();
+ mVibrator = vibrator;
+ mHasVibrator = mVibrator.hasVibrator();
mAudioService = iAudioService;
boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -393,8 +391,7 @@
}
public void vibrate(VibrationEffect effect) {
- mVibrator.ifPresent(
- vibrator -> vibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES));
+ mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES);
}
public boolean hasVibrator() {
@@ -402,7 +399,7 @@
}
private void onNotifyVisibleW(boolean visible) {
- if (mDestroyed) return;
+ if (mDestroyed) return;
mAudio.notifyVolumeControllerVisible(mVolumeController, visible);
if (!visible) {
if (updateActiveStreamW(-1)) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 6bc6505..aa671d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -20,11 +20,12 @@
import org.mockito.Mockito.verify
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,9 +45,11 @@
@Mock
lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
- lateinit var dumpManager: DumpManager
- @Mock
lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
+ @Mock
+ lateinit var sessionTracker: SessionTracker
+ @Mock
+ lateinit var sessionId: InstanceId
@Captor
lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -58,11 +61,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
+ whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId)
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
mContext,
uiEventLogger,
keyguardUpdateMonitor,
- dumpManager)
+ sessionTracker)
}
@Test
@@ -76,7 +80,7 @@
// THEN encrypted / lockdown state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+ .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN, sessionId)
}
@Test
@@ -93,7 +97,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
}
@Test
@@ -110,7 +114,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
}
@Test
@@ -128,9 +132,9 @@
// THEN primary auth required state is logged with all the reasons
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
// WHEN onStrongAuthStateChanged is called again
updateMonitorCallback.onStrongAuthStateChanged(0)
@@ -152,7 +156,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+ .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT, sessionId)
// WHEN face lockout is reset
whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
@@ -160,7 +164,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+ .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET, sessionId)
}
@Test
@@ -176,7 +180,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+ .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT, sessionId)
// WHEN fingerprint lockout is reset
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
@@ -184,7 +188,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+ .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET, sessionId)
}
fun captureUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 599e547..a819a7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -120,6 +121,8 @@
private FeatureFlags mFeatureFlags;
@Mock
private UserSwitcherController mUserSwitcherController;
+ @Mock
+ private SessionTracker mSessionTracker;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -154,7 +157,8 @@
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings).create(mSecurityCallback);
+ mUserSwitcherController, mFeatureFlags, mGlobalSettings,
+ mSessionTracker).create(mSecurityCallback);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 164f83d..6c1f008 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -20,23 +20,21 @@
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT
-import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.util.mockito.capture
-import org.junit.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
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.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
/**
* Translates items away/towards the hinge when the device is opened/closed. This is controlled by
@@ -46,14 +44,11 @@
@RunWith(AndroidTestingRunner::class)
class KeyguardUnfoldTransitionTest : SysuiTestCase() {
- @Mock
- private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+ @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- @Captor
- private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+ @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
- @Mock
- private lateinit var parent: ViewGroup
+ @Mock private lateinit var parent: ViewGroup
private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
private lateinit var progressListener: TransitionProgressListener
@@ -63,87 +58,35 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- xTranslationMax = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_unfold_translation_x).toFloat()
+ xTranslationMax =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
- keyguardUnfoldTransition = KeyguardUnfoldTransition(
- getContext(),
- progressProvider
- )
-
- verify(progressProvider).addCallback(capture(progressListenerCaptor))
- progressListener = progressListenerCaptor.value
+ keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
keyguardUnfoldTransition.setup(parent)
keyguardUnfoldTransition.statusViewCentered = false
- }
- @Test
- fun onTransition_noMatchingIds() {
- // GIVEN no views matching any ids
- // WHEN the transition starts
- progressListener.onTransitionStarted()
- progressListener.onTransitionProgress(.1f)
-
- // THEN nothing... no exceptions
- }
-
- @Test
- fun onTransition_oneMovesLeft() {
- // GIVEN one view with a matching id
- val view = View(getContext())
- `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view)
-
- moveAndValidate(listOf(view to LEFT))
- }
-
- @Test
- fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
- // GIVEN two views with a matching id
- val leftView = View(getContext())
- val rightView = View(getContext())
- `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView)
- `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView)
-
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
- moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ verify(progressProvider).addCallback(capture(progressListenerCaptor))
+ progressListener = progressListenerCaptor.value
}
@Test
fun onTransition_centeredViewDoesNotMove() {
keyguardUnfoldTransition.statusViewCentered = true
- val view = View(getContext())
+ val view = View(context)
`when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
- moveAndValidate(listOf(view to 0))
- }
-
- private fun moveAndValidate(list: List<Pair<View, Int>>) {
- // Compare values as ints because -0f != 0f
-
- // WHEN the transition starts
progressListener.onTransitionStarted()
+ assertThat(view.translationX).isZero()
+
progressListener.onTransitionProgress(0f)
+ assertThat(view.translationX).isZero()
- list.forEach { (view, direction) ->
- assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt())
- }
+ progressListener.onTransitionProgress(0.5f)
+ assertThat(view.translationX).isZero()
- // WHEN the transition progresses, translation is updated
- progressListener.onTransitionProgress(.5f)
- list.forEach { (view, direction) ->
- assertEquals(
- (-xTranslationMax / 2f * direction).toInt(),
- view.getTranslationX().toInt()
- )
- }
-
- // WHEN the transition ends, translation is completed
- progressListener.onTransitionProgress(1f)
progressListener.onTransitionFinished()
- list.forEach { (view, _) ->
- assertEquals(0, view.getTranslationX().toInt())
- }
+ assertThat(view.translationX).isZero()
}
}
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/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index b951345..61e78f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -43,7 +43,7 @@
@Before
fun setUp() {
dialogLaunchAnimator = DialogLaunchAnimator(
- dreamManager, launchAnimator, isForTesting = true)
+ dreamManager, launchAnimator, forceDisableSynchronization = true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 0b399cf..ae1268d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -56,6 +56,7 @@
import android.widget.ScrollView;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import org.junit.Before;
import org.junit.Test;
@@ -75,6 +76,7 @@
private @Mock AuthDialogCallback mCallback;
private @Mock UserManager mUserManager;
+ private @Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Before
public void setup() {
@@ -263,15 +265,17 @@
componentInfo,
FingerprintSensorProperties.TYPE_REAR,
false /* resetLockoutRequiresHardwareAuthToken */));
- mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */);
+ mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */,
+ mWakefulnessLifecycle);
}
private class TestableAuthContainer extends AuthContainerView {
TestableAuthContainer(AuthContainerView.Config config,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
- @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+ @Nullable List<FaceSensorPropertiesInternal> faceProps,
+ WakefulnessLifecycle wakefulnessLifecycle) {
- super(config, new MockInjector(), fpProps, faceProps);
+ super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle);
}
@Override
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 08c7714..c37e966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -27,10 +27,12 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,6 +48,7 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
@@ -69,6 +72,8 @@
import com.android.internal.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -79,6 +84,7 @@
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -98,6 +104,8 @@
@Mock
private IBiometricSysuiReceiver mReceiver;
@Mock
+ private IBiometricContextListener mContextListener;
+ @Mock
private AuthDialog mDialog1;
@Mock
private AuthDialog mDialog2;
@@ -117,10 +125,16 @@
private SidefpsController mSidefpsController;
@Mock
private DisplayManager mDisplayManager;
+ @Mock
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
@Captor
ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor;
+ @Captor
+ ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -172,12 +186,15 @@
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
- () -> mUdfpsController, () -> mSidefpsController);
+ () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
mAuthenticatorsRegisteredCaptor.capture());
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+
mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
// Ensures that the operations posted on the handler get executed.
@@ -195,7 +212,8 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+ mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mStatusBarStateController);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -218,7 +236,8 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController);
+ mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mStatusBarStateController);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -625,6 +644,47 @@
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();
+ }
+
+ @Test
+ public void testForwardsDozeEvent() throws RemoteException {
+ mAuthController.setBiometicContextListener(mContextListener);
+
+ mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
+ mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
+
+ InOrder order = inOrder(mContextListener);
+ // invoked twice since the initial state is false
+ order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
+ order.verify(mContextListener).onDozeChanged(eq(true));
+ }
+
// Helpers
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
@@ -674,17 +734,20 @@
FingerprintManager fingerprintManager,
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
- Provider<SidefpsController> sidefpsControllerFactory) {
+ Provider<SidefpsController> sidefpsControllerFactory,
+ StatusBarStateController statusBarStateController) {
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
- sidefpsControllerFactory, mDisplayManager, mHandler);
+ sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
+ statusBarStateController, mHandler);
}
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
String opPackageName, boolean skipIntro, long operationId, long requestId,
- @BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
+ @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
+ WakefulnessLifecycle wakefulnessLifecycle) {
mLastBiometricPromptInfo = promptInfo;
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/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 159bdba..35e838b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -44,7 +44,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.VibrationAttributes;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -64,6 +63,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -141,7 +141,7 @@
@Mock
private ScreenLifecycle mScreenLifecycle;
@Mock
- private Vibrator mVibrator;
+ private VibratorHelper mVibrator;
@Mock
private UdfpsHapticsSimulator mUdfpsHapticsSimulator;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 55509d1..9908d44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -20,13 +20,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
-import dagger.Lazy
-import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,15 +39,14 @@
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
- private lateinit var uiController: ControlsUiController
- @Mock
- private lateinit var lazyUiController: Lazy<ControlsUiController>
+ private lateinit var vibratorHelper: VibratorHelper
@Mock
private lateinit var keyguardStateController: KeyguardStateController
@Mock
@@ -59,8 +56,6 @@
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
- private lateinit var globalActionsComponent: GlobalActionsComponent
- @Mock
private lateinit var taskViewFactory: Optional<TaskViewFactory>
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var cvh: ControlViewHolder
@@ -86,11 +81,9 @@
uiExecutor,
activityStarter,
keyguardStateController,
- globalActionsComponent,
taskViewFactory,
- getFakeBroadcastDispatcher(),
- lazyUiController,
- metricsLogger
+ metricsLogger,
+ vibratorHelper
))
`when`(cvh.cws.ci.controlId).thenReturn(ID)
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..8adb55b 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,14 @@
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.settingslib.dream.DreamBackend;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.complication.Complication;
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;
@@ -50,6 +52,10 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamOverlayServiceTest extends SysuiTestCase {
@@ -65,10 +71,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,10 +91,22 @@
@Mock
DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+ @Mock
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ @Mock
+ DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+
+ @Mock
+ DreamOverlayStateController mStateController;
+
+ @Mock
+ DreamBackend mDreamBackend;
+
DreamOverlayService mService;
@Before
- public void setup() throws Exception {
+ public void setup() {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
@@ -102,6 +116,10 @@
.thenReturn(mLifecycleOwner);
when(mDreamOverlayComponent.getLifecycleRegistry())
.thenReturn(mLifecycleRegistry);
+ when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
+ .thenReturn(mDreamOverlayTouchMonitor);
+ when(mDreamOverlayComponent.getDreamBackend())
+ .thenReturn(mDreamBackend);
when(mDreamOverlayComponentFactory
.create(any(), any()))
.thenReturn(mDreamOverlayComponent);
@@ -109,29 +127,37 @@
.thenReturn(mDreamOverlayContainerView);
mService = new DreamOverlayService(mContext, mMainExecutor,
- mDreamOverlayComponentFactory);
+ mDreamOverlayComponentFactory,
+ mStateController,
+ mKeyguardUpdateMonitor);
+ }
+
+ @Test
+ public void testOverlayContainerViewAddedToWindow() throws Exception {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback);
mMainExecutor.runAllReady();
- }
- @Test
- public void testOverlayContainerViewAddedToWindow() {
verify(mWindowManager).addView(any(), any());
}
@Test
- public void testDreamOverlayContainerViewControllerInitialized() {
+ public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
verify(mDreamOverlayContainerViewController).init();
}
@Test
public void testShouldShowComplicationsTrueByDefault() {
- assertThat(mService.shouldShowComplications()).isTrue();
-
mService.onBind(new Intent());
assertThat(mService.shouldShowComplications()).isTrue();
@@ -145,4 +171,24 @@
assertThat(mService.shouldShowComplications()).isFalse();
}
+
+ @Test
+ public void testSetAvailableComplicationTypes() throws Exception {
+ final Set<Integer> enabledComplications = new HashSet<>(
+ Arrays.asList(DreamBackend.COMPLICATION_TYPE_TIME,
+ DreamBackend.COMPLICATION_TYPE_DATE,
+ DreamBackend.COMPLICATION_TYPE_WEATHER));
+ when(mDreamBackend.getEnabledComplications()).thenReturn(enabledComplications);
+
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ overlay.startDream(mWindowParams, mDreamOverlayCallback);
+ mMainExecutor.runAllReady();
+
+ final int expectedTypes =
+ Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_DATE
+ | Complication.COMPLICATION_TYPE_WEATHER;
+ verify(mStateController).setAvailableComplicationTypes(expectedTypes);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7d0833d..627da3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -16,11 +16,15 @@
package com.android.systemui.dreams;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -35,6 +39,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Collection;
@@ -56,7 +61,29 @@
}
@Test
- public void testCallback() throws Exception {
+ public void testStateChange() {
+ final DreamOverlayStateController stateController = new DreamOverlayStateController(
+ mExecutor);
+ stateController.addCallback(mCallback);
+ stateController.setOverlayActive(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback).onStateChanged();
+ assertThat(stateController.isOverlayActive()).isTrue();
+
+ Mockito.clearInvocations(mCallback);
+ stateController.setOverlayActive(true);
+ mExecutor.runAllReady();
+ verify(mCallback, never()).onStateChanged();
+
+ stateController.setOverlayActive(false);
+ mExecutor.runAllReady();
+ verify(mCallback).onStateChanged();
+ assertThat(stateController.isOverlayActive()).isFalse();
+ }
+
+ @Test
+ public void testCallback() {
final DreamOverlayStateController stateController = new DreamOverlayStateController(
mExecutor);
stateController.addCallback(mCallback);
@@ -94,4 +121,43 @@
mExecutor.runAllReady();
verify(mCallback, times(1)).onComplicationsChanged();
}
+
+ @Test
+ public void testComplicationFiltering() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
+ final Complication weatherComplication = Mockito.mock(Complication.class);
+ when(alwaysAvailableComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_NONE);
+ when(weatherComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
+
+ stateController.addComplication(alwaysAvailableComplication);
+ stateController.addComplication(weatherComplication);
+
+ final DreamOverlayStateController.Callback callback =
+ Mockito.mock(DreamOverlayStateController.Callback.class);
+
+ stateController.addCallback(callback);
+ mExecutor.runAllReady();
+
+ {
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isFalse();
+ }
+
+ stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
+ mExecutor.runAllReady();
+ verify(callback).onAvailableComplicationTypesChanged();
+
+ {
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isTrue();
+ }
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
new file mode 100644
index 0000000..3b17a80
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+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.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SmartSpaceComplicationTest extends SysuiTestCase {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private LockscreenSmartspaceController mSmartspaceController;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private SmartSpaceComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+ */
+ @Test
+ public void testAvailability() {
+ when(mSmartspaceController.isEnabled()).thenReturn(false);
+
+ final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication,
+ mSmartspaceController);
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
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..5fcf414 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
@@ -75,6 +75,10 @@
callbackCaptor.getValue().onComplicationsChanged();
verifyUpdate(observer, complications);
+
+ callbackCaptor.getValue().onAvailableComplicationTypesChanged();
+
+ verifyUpdate(observer, complications);
});
}
@@ -85,7 +89,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/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index f227a9b..d5ab708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -27,6 +27,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -122,6 +123,34 @@
}
/**
+ * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+ */
+ @Test
+ public void testSnapToGuide() {
+ final ViewInfo firstViewInfo = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ true),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+ addComplication(engine, firstViewInfo);
+
+ // Ensure the view is added to the top end corner
+ verifyChange(firstViewInfo, true, lp -> {
+ assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+ assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue();
+ });
+ }
+
+ /**
* Ensures layout in a particular direction updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index 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/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
new file mode 100644
index 0000000..365c529
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
+import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
+import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationTypes;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationUtilsTest extends SysuiTestCase {
+ @Test
+ public void testConvertComplicationType() {
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME))
+ .isEqualTo(COMPLICATION_TYPE_TIME);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE))
+ .isEqualTo(COMPLICATION_TYPE_DATE);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_WEATHER))
+ .isEqualTo(COMPLICATION_TYPE_WEATHER);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_AIR_QUALITY))
+ .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
+ .isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+ }
+
+ @Test
+ public void testConvertComplicationTypesEmpty() {
+ final Set<Integer> input = new HashSet<>();
+ final int expected = Complication.COMPLICATION_TYPE_NONE;
+
+ assertThat(convertComplicationTypes(input)).isEqualTo(expected);
+ }
+
+ @Test
+ public void testConvertComplicationTypesSingleValue() {
+ final Set<Integer> input = new HashSet<>(
+ Collections.singleton(DreamBackend.COMPLICATION_TYPE_WEATHER));
+ final int expected = Complication.COMPLICATION_TYPE_WEATHER;
+
+ assertThat(convertComplicationTypes(input)).isEqualTo(expected);
+ }
+
+ @Test
+ public void testConvertComplicationTypesSingleValueMultipleValues() {
+ final Set<Integer> input = new HashSet<>(
+ Arrays.asList(DreamBackend.COMPLICATION_TYPE_TIME,
+ DreamBackend.COMPLICATION_TYPE_DATE,
+ DreamBackend.COMPLICATION_TYPE_WEATHER,
+ DreamBackend.COMPLICATION_TYPE_AIR_QUALITY,
+ DreamBackend.COMPLICATION_TYPE_CAST_INFO));
+ final int expected =
+ Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_DATE
+ | Complication.COMPLICATION_TYPE_WEATHER | COMPLICATION_TYPE_AIR_QUALITY
+ | COMPLICATION_TYPE_CAST_INFO;
+
+ assertThat(convertComplicationTypes(input)).isEqualTo(expected);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
new file mode 100644
index 0000000..b02c506
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockDateComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamClockDateComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamClockDateComplication} is registered.
+ */
+ @Test
+ public void testComplicationAdded() {
+ final DreamClockDateComplication.Registrant registrant =
+ new DreamClockDateComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
new file mode 100644
index 0000000..088b4d5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamClockTimeComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamClockTimeComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamClockTimeComplication} is registered.
+ */
+ @Test
+ public void testComplicationAdded() {
+ final DreamClockTimeComplication.Registrant registrant =
+ new DreamClockTimeComplication.Registrant(
+ mContext,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
new file mode 100644
index 0000000..151742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.any;
+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.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamWeatherComplicationTest extends SysuiTestCase {
+ @SuppressWarnings("HidingField")
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private LockscreenSmartspaceController mSmartspaceController;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private DreamWeatherComplication mComplication;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Ensures {@link DreamWeatherComplication} is only registered when it is available.
+ */
+ @Test
+ public void testComplicationAvailability() {
+ when(mSmartspaceController.isEnabled()).thenReturn(false);
+ final DreamWeatherComplication.Registrant registrant =
+ new DreamWeatherComplication.Registrant(
+ mContext,
+ mSmartspaceController,
+ mDreamOverlayStateController,
+ mComplication);
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ registrant.start();
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..1a8326f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ @Mock
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+ @Mock
+ StatusBar mStatusBar;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ DreamTouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float SCREEN_HEIGHT_PX = 100;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mStatusBarKeyguardViewManager,
+ mStatusBar,
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION);
+ when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ }
+
+ private static void beginValidSwipe(GestureDetector.OnGestureListener listener) {
+ listener.onDown(MotionEvent.obtain(0, 0,
+ MotionEvent.ACTION_DOWN, 0,
+ SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0));
+ }
+
+ /**
+ * Ensures expansion only happens when touch down happens in valid part of the screen.
+ */
+ @Test
+ public void testSessionStart() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
+ ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
+
+ final Random random = new Random(System.currentTimeMillis());
+
+ // If an initial touch down meeting criteria has been met, scroll behavior should be
+ // ignored.
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isFalse();
+
+ // A touch at the top of the screen should also not trigger listening.
+ final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
+ 0, 0, 0);
+
+ gestureListenerCaptor.getValue().onDown(touchDownEvent);
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isFalse();
+
+ // A touch within range at the bottom of the screen should trigger listening
+ beginValidSwipe(gestureListenerCaptor.getValue());
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ random.nextFloat(),
+ random.nextFloat())).isTrue();
+ }
+
+ /**
+ * Makes sure expansion amount is proportional to scroll.
+ */
+ @Test
+ public void testExpansionAmount() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ beginValidSwipe(gestureListenerCaptor.getValue());
+
+ final float scrollAmount = .3f;
+ final float distanceY = SCREEN_HEIGHT_PX * scrollAmount;
+
+ final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX, 0);
+ final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+ assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+ .isTrue();
+
+ // Ensure only called once
+ verify(mStatusBarKeyguardViewManager)
+ .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
+
+ // Ensure correct expansion passed in.
+ verify(mStatusBarKeyguardViewManager)
+ .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true));
+ }
+
+ private void swipeToPosition(float position, float velocityY) {
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
+
+ when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
+
+ beginValidSwipe(gestureListenerCaptor.getValue());
+
+ final float distanceY = SCREEN_HEIGHT_PX * position;
+
+ final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX, 0);
+ final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+ 0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+ assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY))
+ .isTrue();
+
+ final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+ 0, 0, 0);
+
+ inputEventListenerCaptor.getValue().onInputEvent(upEvent);
+ }
+
+ /**
+ * Tests that ending a swipe before the set expansion threshold leads to bouncer collapsing
+ * down.
+ */
+ @Test
+ public void testCollapseOnThreshold() {
+ final float swipeUpPercentage = .3f;
+ swipeToPosition(swipeUpPercentage, -1);
+
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncer.EXPANSION_VISIBLE));
+ verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ verify(mValueAnimator).start();
+ }
+
+ /**
+ * Tests that ending a swipe above the set expansion threshold will continue the expansion.
+ */
+ @Test
+ public void testExpandOnThreshold() {
+ final float swipeUpPercentage = .7f;
+ swipeToPosition(swipeUpPercentage, 1);
+
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ verify(mValueAnimator).start();
+ }
+}
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/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
new file mode 100644
index 0000000..2cb1939
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dump
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
+
+/**
+ * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
+ */
+fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
+ LogBuffer(name, 50, 50, LogcatEchoTrackerAlways())
+
+/**
+ * A [LogcatEchoTracker] that always allows echoing to the logcat.
+ */
+class LogcatEchoTrackerAlways : LogcatEchoTracker {
+ override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true
+ override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e3a7e3b..71fc8ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -115,6 +116,7 @@
@Mock private PackageManager mPackageManager;
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
+ @Mock private VibratorHelper mVibratorHelper;
@Mock private StatusBar mStatusBar;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
@@ -143,7 +145,7 @@
mTelephonyListenerManager,
mGlobalSettings,
mSecureSettings,
- null,
+ mVibratorHelper,
mResources,
mConfigurationController,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
new file mode 100644
index 0000000..1171bd2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase {
+
+ private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
+
+ @Mock
+ private Executor mExecutor;
+
+ @Mock
+ private SecureSettings mSecureSettings;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mSecureSettings.getString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST)).thenReturn(null);
+ mHdmiCecSetMenuLanguageHelper =
+ new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings);
+ }
+
+ @Test
+ public void testSetGetLocale() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("en");
+ assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH);
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_EmptyByDefault() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("en");
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_AcceptLanguage() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.acceptLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
+ verify(mExecutor).execute(any());
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_DeclineLanguage() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+ }
+
+ @Test
+ public void testIsLocaleDenylisted_DeclineTwoLanguages() {
+ mHdmiCecSetMenuLanguageHelper.setLocale("de");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
+ mHdmiCecSetMenuLanguageHelper.setLocale("pl");
+ mHdmiCecSetMenuLanguageHelper.declineLocale();
+ assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
+ verify(mSecureSettings).putString(
+ Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl");
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9827d21..210cb82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -46,11 +46,14 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -68,6 +71,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.Lazy;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -94,6 +99,9 @@
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+ private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+ private @Mock DreamOverlayStateController mDreamOverlayStateController;
+ private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -197,7 +205,10 @@
mScreenOffAnimationController,
() -> mNotificationShadeDepthController,
mScreenOnCoordinator,
- mInteractionJankMonitor);
+ mInteractionJankMonitor,
+ mDreamOverlayStateController,
+ mNotificationShadeWindowControllerLazy,
+ () -> mActivityLaunchAnimator);
mViewMediator.start();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index d7c00fb..5ed1d65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -38,7 +38,6 @@
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
@@ -62,6 +61,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,7 +104,7 @@
private @Mock DumpManager mDumpManager;
private @Mock AccessibilityManager mAccessibilityManager;
private @Mock ConfigurationController mConfigurationController;
- private @Mock Vibrator mVibrator;
+ private @Mock VibratorHelper mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
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/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 43d9a75..dc7026d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -95,7 +95,6 @@
fun testVisibleOnKeyguardOrFullScreenUserSwitcher() {
testStateVisibility(StatusBarState.SHADE, GONE)
testStateVisibility(StatusBarState.SHADE_LOCKED, GONE)
- testStateVisibility(StatusBarState.FULLSCREEN_USER_SWITCHER, VISIBLE)
testStateVisibility(StatusBarState.KEYGUARD, VISIBLE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 140a395..609291a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -125,8 +125,9 @@
private lateinit var settings: View
private lateinit var settingsText: TextView
private lateinit var cancel: View
+ private lateinit var cancelText: TextView
private lateinit var dismiss: FrameLayout
- private lateinit var dismissLabel: View
+ private lateinit var dismissText: TextView
private lateinit var session: MediaSession
private val device = MediaDeviceData(true, null, DEVICE_NAME)
@@ -163,8 +164,9 @@
settings = View(context)
settingsText = TextView(context)
cancel = View(context)
+ cancelText = TextView(context)
dismiss = FrameLayout(context)
- dismissLabel = View(context)
+ dismissText = TextView(context)
initPlayerHolderMocks()
initSessionHolderMocks()
@@ -244,13 +246,15 @@
whenever(holder.settings).thenReturn(settings)
whenever(holder.settingsText).thenReturn(settingsText)
whenever(holder.cancel).thenReturn(cancel)
+ whenever(holder.cancelText).thenReturn(cancelText)
whenever(holder.dismiss).thenReturn(dismiss)
- whenever(holder.dismissLabel).thenReturn(dismissLabel)
+ whenever(holder.dismissText).thenReturn(dismissText)
}
/** Mock view holder for session player */
private fun initSessionHolderMocks() {
whenever(sessionHolder.player).thenReturn(view)
+ whenever(sessionHolder.albumView).thenReturn(albumView)
whenever(sessionHolder.appIcon).thenReturn(appIcon)
whenever(sessionHolder.titleText).thenReturn(titleText)
whenever(sessionHolder.artistText).thenReturn(artistText)
@@ -284,8 +288,9 @@
whenever(sessionHolder.settings).thenReturn(settings)
whenever(sessionHolder.settingsText).thenReturn(settingsText)
whenever(sessionHolder.cancel).thenReturn(cancel)
+ whenever(sessionHolder.cancelText).thenReturn(cancelText)
whenever(sessionHolder.dismiss).thenReturn(dismiss)
- whenever(sessionHolder.dismissLabel).thenReturn(dismissLabel)
+ whenever(sessionHolder.dismissText).thenReturn(dismissText)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index a3ffb2f..97b3b10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -85,6 +86,8 @@
private lateinit var configurationController: ConfigurationController
@Mock
private lateinit var uniqueObjectHostView: UniqueObjectHostView
+ @Mock
+ private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -110,7 +113,8 @@
notificationLockscreenUserManager,
configurationController,
wakefulnessLifecycle,
- statusBarKeyguardViewManager)
+ statusBarKeyguardViewManager,
+ dreamOverlayStateController)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
new file mode 100644
index 0000000..29188da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.util.animation.UniqueObjectHostView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaComplicationViewControllerTest extends SysuiTestCase {
+ @Mock
+ private MediaHost mMediaHost;
+
+ @Mock
+ private UniqueObjectHostView mView;
+
+ @Mock
+ private FrameLayout mComplicationContainer;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMediaHost.hostView = mView;
+ }
+
+ @Test
+ public void testMediaHostViewInteraction() {
+ final MediaComplicationViewController controller = new MediaComplicationViewController(
+ mComplicationContainer, mMediaHost);
+
+ controller.init();
+
+ controller.onViewAttached();
+ verify(mComplicationContainer).addView(eq(mView));
+
+ controller.onViewDetached();
+ verify(mComplicationContainer).removeView(eq(mView));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
new file mode 100644
index 0000000..114fc90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dream;
+
+import static org.mockito.ArgumentMatchers.any;
+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 androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaDreamSentinelTest extends SysuiTestCase {
+ @Mock
+ MediaDataManager mMediaDataManager;
+
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ MediaDreamComplication mComplication;
+
+ final String mKey = "key";
+ final String mOldKey = "old_key";
+
+ @Mock
+ MediaData mData;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testComplicationAddition() {
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ mDreamOverlayStateController, mComplication);
+
+ sentinel.start();
+
+ ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+ verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+ final MediaDataManager.Listener listener = listenerCaptor.getValue();
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+ listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ listener.onMediaDataRemoved(mKey);
+ verify(mDreamOverlayStateController, never()).removeComplication(any());
+
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+ listener.onMediaDataRemoved(mKey);
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
new file mode 100644
index 0000000..c261086
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt
@@ -0,0 +1,124 @@
+package com.android.systemui.media.nearby
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.media.INearbyMediaDevicesProvider
+import com.android.systemui.shared.media.INearbyMediaDevicesService
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_LONG
+import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
+import com.android.systemui.shared.media.NearbyDevice
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class NearbyMediaDevicesServiceTest : SysuiTestCase() {
+
+ private lateinit var service: NearbyMediaDevicesService
+ private lateinit var binderInterface: INearbyMediaDevicesService
+
+ @Before
+ fun setUp() {
+ service = NearbyMediaDevicesService()
+ binderInterface = INearbyMediaDevicesService.Stub.asInterface(service.onBind(null))
+ }
+
+ @Test
+ fun getCurrentNearbyDevices_noProviderRegistered_returnsEmptyList() {
+ assertThat(service.getCurrentNearbyDevices()).isEmpty()
+ }
+
+ @Test
+ fun getCurrentNearbyDevices_providerRegistered_returnsProviderInfo() {
+ val nearbyDevice1 = NearbyDevice("routeId1", RANGE_LONG)
+ val nearbyDevice2 = NearbyDevice("routeId2", RANGE_WITHIN_REACH)
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> {
+ return listOf(nearbyDevice1, nearbyDevice2)
+ }
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ }
+ binderInterface.registerProvider(provider)
+
+ val returnedNearbyDevices = service.getCurrentNearbyDevices()
+
+ assertThat(returnedNearbyDevices).isEqualTo(listOf(nearbyDevice1, nearbyDevice2))
+ }
+
+ @Test
+ fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
+ // No assert, just needs no crash
+ service.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ })
+ }
+
+ @Test
+ fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ var registeredCallback: INearbyMediaDevicesUpdateCallback? = null
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {
+ registeredCallback = callback
+ }
+
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+ }
+ binderInterface.registerProvider(provider)
+
+ val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ }
+
+ service.registerNearbyDevicesCallback(callback)
+
+ assertThat(provider.registeredCallback).isEqualTo(callback)
+ }
+
+ @Test
+ fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
+ // No assert, just needs no crash
+ service.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ })
+ }
+
+ @Test
+ fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
+ val provider = object : INearbyMediaDevicesProvider.Stub() {
+ var unregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
+ override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
+
+ override fun registerNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {}
+
+ override fun unregisterNearbyDevicesCallback(
+ callback: INearbyMediaDevicesUpdateCallback?
+ ) {
+ unregisteredCallback = callback
+ }
+ }
+ binderInterface.registerProvider(provider)
+
+ val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
+ override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
+ }
+
+ service.unregisterNearbyDevicesCallback(callback)
+
+ assertThat(provider.unregisteredCallback).isEqualTo(callback)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 81ae209..c3d8d24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -16,27 +16,35 @@
package com.android.systemui.media.taptotransfer
-import android.content.ComponentName
+import android.app.StatusBarManager
+import android.content.Context
+import android.media.MediaRoute2Info
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
+import com.android.systemui.media.taptotransfer.sender.AlmostCloseToEndCast
+import com.android.systemui.media.taptotransfer.sender.AlmostCloseToStartCast
+import com.android.systemui.media.taptotransfer.sender.TransferFailed
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.anyString
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import java.io.PrintWriter
import java.io.StringWriter
@@ -53,24 +61,19 @@
private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper
@Mock
- private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
+ private lateinit var statusBarManager: StatusBarManager
@Mock
- private lateinit var mediaSenderService: IDeviceSenderService.Stub
- private lateinit var mediaSenderServiceComponentName: ComponentName
+ private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java)
- context.addMockService(mediaSenderServiceComponentName, mediaSenderService)
- whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService)
- whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService)
-
+ context.addMockSystemService(Context.STATUS_BAR_SERVICE, statusBarManager)
mediaTttCommandLineHelper =
MediaTttCommandLineHelper(
commandRegistry,
context,
+ FakeExecutor(FakeSystemClock()),
mediaTttChipControllerReceiver,
)
}
@@ -101,85 +104,113 @@
}
@Test
- fun sender_moveCloserToStartCast_serviceCallbackCalled() {
- commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
+ fun sender_almostCloseToStartCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(AlmostCloseToStartCast::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
-
- val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
- verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor))
- assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ val routeInfoCaptor = argumentCaptor<MediaRoute2Info>()
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST),
+ capture(routeInfoCaptor),
+ nullable(),
+ nullable())
+ assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
- fun sender_moveCloserToEndCast_serviceCallbackCalled() {
- commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand())
+ fun sender_almostCloseToEndCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(AlmostCloseToEndCast::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
-
- val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
- verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor))
- assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ val routeInfoCaptor = argumentCaptor<MediaRoute2Info>()
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST),
+ capture(routeInfoCaptor),
+ nullable(),
+ nullable())
+ assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
fun sender_transferToReceiverTriggered_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferToReceiverTriggeredCommand())
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(TransferToReceiverTriggered::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
-
- val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
- verify(mediaSenderService).transferToReceiverTriggered(any(), capture(deviceInfoCaptor))
- assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ val routeInfoCaptor = argumentCaptor<MediaRoute2Info>()
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED),
+ capture(routeInfoCaptor),
+ nullable(),
+ nullable())
+ assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand())
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(TransferToThisDeviceTriggered::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
- verify(mediaSenderService).transferToThisDeviceTriggered(any(), any())
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED),
+ any(),
+ nullable(),
+ nullable())
}
@Test
fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand())
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(TransferToReceiverSucceeded::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
-
- val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
- verify(mediaSenderService)
- .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any())
- assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ val routeInfoCaptor = argumentCaptor<MediaRoute2Info>()
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED),
+ capture(routeInfoCaptor),
+ nullable(),
+ nullable())
+ assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() {
- commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand())
+ commandRegistry.onShellCommand(
+ pw, getSenderCommand(TransferToThisDeviceSucceeded::class.simpleName!!)
+ )
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
-
- val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
- verify(mediaSenderService)
- .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any())
- assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ val routeInfoCaptor = argumentCaptor<MediaRoute2Info>()
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED),
+ capture(routeInfoCaptor),
+ nullable(),
+ nullable())
+ assertThat(routeInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
@Test
fun sender_transferFailed_serviceCallbackCalled() {
- commandRegistry.onShellCommand(pw, getTransferFailedCommand())
+ commandRegistry.onShellCommand(pw, getSenderCommand(TransferFailed::class.simpleName!!))
- assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
- verify(mediaSenderService).transferFailed(any(), any())
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED),
+ any(),
+ nullable(),
+ nullable())
}
@Test
- fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() {
- commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand())
+ fun sender_farFromReceiver_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getSenderCommand(FAR_FROM_RECEIVER_STATE))
- // Once we're no longer close to the receiver, we should unbind the service.
- assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse()
- verify(mediaSenderService).noLongerCloseToReceiver(any(), any())
+ verify(statusBarManager).updateMediaTapToTransferSenderDisplay(
+ eq(StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER),
+ any(),
+ nullable(),
+ nullable())
}
@Test
@@ -196,61 +227,8 @@
verify(mediaTttChipControllerReceiver).removeChip()
}
- private fun getMoveCloserToStartCastCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
- )
-
- private fun getMoveCloserToEndCastCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- MOVE_CLOSER_TO_END_CAST_COMMAND_NAME
- )
-
- private fun getTransferToReceiverTriggeredCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME
- )
-
- private fun getTransferToThisDeviceTriggeredCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME
- )
-
- private fun getTransferToReceiverSucceededCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME
- )
-
- private fun getTransferToThisDeviceSucceededCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME
- )
-
- private fun getTransferFailedCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- TRANSFER_FAILED_COMMAND_NAME
- )
-
- private fun getNoLongerCloseToReceiverCommand(): Array<String> =
- arrayOf(
- SENDER_COMMAND,
- DEVICE_NAME,
- NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
- )
+ private fun getSenderCommand(displayState: String): Array<String> =
+ arrayOf(SENDER_COMMAND, DEVICE_NAME, displayState)
class EmptyCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 6b4eebe..c74ac64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -16,17 +16,20 @@
package com.android.systemui.media.taptotransfer.sender
+import android.app.StatusBarManager
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -34,6 +37,7 @@
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -46,17 +50,150 @@
@Mock
private lateinit var windowManager: WindowManager
+ @Mock
+ private lateinit var commandQueue: CommandQueue
+ private lateinit var commandQueueCallback: CommandQueue.Callbacks
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
- controllerSender = MediaTttChipControllerSender(context, windowManager)
+ controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager)
+
+ val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ verify(commandQueue).addCallback(callbackCaptor.capture())
+ commandQueueCallback = callbackCaptor.value!!
}
@Test
- fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = moveCloserToStartCast()
+ fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(almostCloseToStartCast().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(almostCloseToEndCast().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToReceiverTriggered().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToReceiverSucceeded().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferToThisDeviceSucceeded().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferFailed().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ assertThat(getChipView().getChipText())
+ .isEqualTo(transferFailed().getChipTextString(context))
+ }
+
+ @Test
+ fun commandQueueCallback_farFromReceiver_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
+ fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = almostCloseToStartCast()
controllerSender.displayChip(state)
val chipView = getChipView()
@@ -69,8 +206,8 @@
}
@Test
- fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = moveCloserToEndCast()
+ fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+ val state = almostCloseToEndCast()
controllerSender.displayChip(state)
val chipView = getChipView()
@@ -133,7 +270,7 @@
@Test
fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
@@ -146,7 +283,7 @@
@Test
fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
var undoCallbackCalled = false
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
undoCallbackCalled = true
}
@@ -160,7 +297,7 @@
@Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
@@ -194,7 +331,7 @@
@Test
fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
@@ -207,7 +344,7 @@
@Test
fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
var undoCallbackCalled = false
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {
undoCallbackCalled = true
}
@@ -221,7 +358,7 @@
@Test
fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
- val undoCallback = object : IUndoTransferCallback.Stub() {
+ val undoCallback = object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
@@ -247,8 +384,8 @@
}
@Test
- fun changeFromCloserToStartToTransferTriggered_loadingIconAppears() {
- controllerSender.displayChip(moveCloserToStartCast())
+ fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
+ controllerSender.displayChip(almostCloseToStartCast())
controllerSender.displayChip(transferToReceiverTriggered())
assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
@@ -267,7 +404,7 @@
controllerSender.displayChip(transferToReceiverTriggered())
controllerSender.displayChip(
transferToReceiverSucceeded(
- object : IUndoTransferCallback.Stub() {
+ object : IUndoMediaTransferCallback.Stub() {
override fun onUndoTriggered() {}
}
)
@@ -277,9 +414,9 @@
}
@Test
- fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
+ fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
controllerSender.displayChip(transferToReceiverSucceeded())
- controllerSender.displayChip(moveCloserToStartCast())
+ controllerSender.displayChip(almostCloseToStartCast())
assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
}
@@ -311,12 +448,12 @@
}
/** Helper method providing default parameters to not clutter up the tests. */
- private fun moveCloserToStartCast() =
- MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ private fun almostCloseToStartCast() =
+ AlmostCloseToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun moveCloserToEndCast() =
- MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ private fun almostCloseToEndCast() =
+ AlmostCloseToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToReceiverTriggered() =
@@ -327,13 +464,13 @@
TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) =
+ private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToReceiverSucceeded(
appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
)
/** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) =
+ private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToThisDeviceSucceeded(
appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
)
@@ -344,3 +481,7 @@
private const val DEVICE_NAME = "My Tablet"
private const val APP_ICON_CONTENT_DESC = "Content description"
+
+private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
+ .addFeature("feature")
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
deleted file mode 100644
index 64542cb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.android.systemui.media.taptotransfer.sender
-
-import android.media.MediaRoute2Info
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderService
-import com.android.systemui.shared.mediattt.IUndoTransferCallback
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@Ignore("b/216286227")
-class MediaTttSenderServiceTest : SysuiTestCase() {
-
- private lateinit var service: IDeviceSenderService
-
- @Mock
- private lateinit var controller: MediaTttChipControllerSender
-
- private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
- .addFeature("feature")
- .build()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- val mediaTttSenderService = MediaTttSenderService(context, controller)
- service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null))
- }
-
- @Test
- fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() {
- val name = "Fake name"
- service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
-
- val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
- verify(controller).displayChip(capture(chipStateCaptor))
-
- val chipState = chipStateCaptor.value!!
- assertThat(chipState.getChipTextString(context)).contains(name)
- }
-
- @Test
- fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() {
- val name = "Fake name"
- service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name))
-
- val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>()
- verify(controller).displayChip(capture(chipStateCaptor))
-
- val chipState = chipStateCaptor.value!!
- assertThat(chipState.getChipTextString(context)).contains(name)
- }
-
- @Test
- fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() {
- service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name"))
-
- verify(controller).displayChip(any<TransferToThisDeviceTriggered>())
- }
-
- @Test
- fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() {
- val name = "Fake name"
- service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name))
-
- val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>()
- verify(controller).displayChip(capture(chipStateCaptor))
-
- val chipState = chipStateCaptor.value!!
- assertThat(chipState.getChipTextString(context)).contains(name)
- }
-
- @Test
- fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() {
- val name = "Fake name"
- val undoCallback = object : IUndoTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback)
-
- val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>()
- verify(controller).displayChip(capture(chipStateCaptor))
-
- val chipState = chipStateCaptor.value!!
- assertThat(chipState.getChipTextString(context)).contains(name)
- assertThat(chipState.undoCallback).isEqualTo(undoCallback)
- }
-
- @Test
- fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() {
- val undoCallback = object : IUndoTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback)
-
- val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>()
- verify(controller).displayChip(capture(chipStateCaptor))
-
- val chipState = chipStateCaptor.value!!
- assertThat(chipState.undoCallback).isEqualTo(undoCallback)
- }
-
- @Test
- fun transferFailed_controllerTriggeredWithTransferFailedState() {
- service.transferFailed(mediaInfo, DeviceInfo("Fake name"))
-
- verify(controller).displayChip(any<TransferFailed>())
- }
-
- @Test
- fun noLongerCloseToReceiver_controllerRemoveChipTriggered() {
- service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name"))
-
- verify(controller).removeChip()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index e73e5ff..721809c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -28,7 +28,6 @@
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Handler;
@@ -81,9 +80,9 @@
private static final int OLD_BATTERY_LEVEL_10 = 10;
private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15);
public static final int BATTERY_LEVEL_10 = 10;
- private WarningsUI mMockWarnings;
+ @Mock private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
- private EnhancedEstimates mEnhancedEstimates;
+ @Mock private EnhancedEstimates mEnhancedEstimates;
@Mock private PowerManager mPowerManager;
@Mock private IThermalService mThermalServiceMock;
private IThermalEventListener mUsbThermalEventListener;
@@ -96,13 +95,9 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
- mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
when(mStatusBarOptionalLazy.get()).thenReturn(Optional.of(mStatusBar));
- mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
-
createPowerUi();
mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
@@ -690,7 +685,8 @@
private void createPowerUi() {
mPowerUI = new PowerUI(
- mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarOptionalLazy);
+ mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarOptionalLazy,
+ mMockWarnings, mEnhancedEstimates, mPowerManager);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 354bb51..f5fa0d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -15,9 +15,10 @@
import com.android.systemui.Dependency
import com.android.systemui.R
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.FooterActionsController.ExpansionState
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -54,12 +55,16 @@
@Mock
private lateinit var userInfoController: UserInfoController
@Mock
+ private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
+ @Mock
private lateinit var multiUserSwitchController: MultiUserSwitchController
@Mock
private lateinit var globalActionsDialog: GlobalActionsDialogLite
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
+ private lateinit var featureFlags: FeatureFlags
+
private lateinit var controller: FooterActionsController
private val metricsLogger: MetricsLogger = FakeMetricsLogger()
@@ -76,15 +81,18 @@
injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
+ whenever(multiUserSwitchControllerFactory.create(any()))
+ .thenReturn(multiUserSwitchController)
+ whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false)
+
view = LayoutInflater.from(context)
.inflate(R.layout.footer_actions, null) as FooterActionsView
- controller = FooterActionsController(view, activityStarter,
- userManager, userTracker, userInfoController, multiUserSwitchController,
+ controller = FooterActionsController(view, multiUserSwitchControllerFactory,
+ activityStarter, userManager, userTracker, userInfoController,
deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
- globalActionsDialog, uiEventLogger, showPMLiteButton = true,
- buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings,
- Handler(testableLooper.looper))
+ globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings,
+ Handler(testableLooper.looper), featureFlags)
controller.init()
ViewUtils.attachView(view)
// View looper is the testable looper associated with the test
@@ -98,7 +106,7 @@
@Test
fun testLogPowerMenuClick() {
- controller.expanded = true
+ controller.visible = true
falsingManager.setFalseTap(false)
view.findViewById<View>(R.id.pm_lite).performClick()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index f43e68f..26aa37d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -61,12 +61,8 @@
@Mock
private ClipboardManager mClipboardManager;
@Mock
- private QuickQSPanelController mQuickQSPanelController;
- @Mock
private TextView mBuildText;
@Mock
- private FooterActionsController mFooterActionsController;
- @Mock
private FalsingManager mFalsingManager;
@Mock
private ActivityStarter mActivityStarter;
@@ -93,8 +89,7 @@
when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
- mActivityStarter, mQSPanelController, mQuickQSPanelController,
- mFooterActionsController);
+ mActivityStarter, mQSPanelController);
mController.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 59948d3..09c6d9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -68,8 +68,6 @@
private lateinit var tileView: QSTileView
@Mock
private lateinit var quickQsBrightnessController: QuickQSBrightnessController
- @Mock
- private lateinit var footerActionsController: FooterActionsController
@Captor
private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
@@ -95,8 +93,7 @@
uiEventLogger,
qsLogger,
dumpManager,
- quickQsBrightnessController,
- footerActionsController
+ quickQsBrightnessController
)
controller.init()
@@ -128,14 +125,12 @@
}
@Test
- fun testBrightnessAndFooterVisibilityRefreshedWhenConfigurationChanged() {
+ fun testBrightnessRefreshedWhenConfigurationChanged() {
// times(2) because both controller and base controller are registering their listeners
verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
verify(quickQsBrightnessController).refreshVisibility(anyBoolean())
- // times(2) because footer visibility is also refreshed on controller init
- verify(footerActionsController, times(2)).refreshVisibility(anyBoolean())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
new file mode 100644
index 0000000..3231415
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -0,0 +1,117 @@
+package com.android.systemui.shared.animation
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
+import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import org.junit.Assert.assertEquals
+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.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
+
+ @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+
+ @Mock private lateinit var parent: ViewGroup
+
+ @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+ private lateinit var animator: UnfoldConstantTranslateAnimator
+ private lateinit var progressListener: TransitionProgressListener
+
+ private val viewsIdToRegister =
+ setOf(
+ ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT),
+ ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT))
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ animator =
+ UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
+
+ animator.init(parent, MAX_TRANSLATION)
+
+ verify(progressProvider).addCallback(progressListenerCaptor.capture())
+ progressListener = progressListenerCaptor.value
+ }
+
+ @Test
+ fun onTransition_noMatchingIds() {
+ // GIVEN no views matching any ids
+ // WHEN the transition starts
+ progressListener.onTransitionStarted()
+ progressListener.onTransitionProgress(.1f)
+
+ // THEN nothing... no exceptions
+ }
+
+ @Test
+ fun onTransition_oneMovesLeft() {
+ // GIVEN one view with a matching id
+ val view = View(context)
+ whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view)
+
+ moveAndValidate(listOf(view to LEFT))
+ }
+
+ @Test
+ fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() {
+ // GIVEN two views with a matching id
+ val leftView = View(context)
+ val rightView = View(context)
+ whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView)
+ whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView)
+
+ moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT))
+ }
+
+ private fun moveAndValidate(list: List<Pair<View, Int>>) {
+ // Compare values as ints because -0f != 0f
+
+ // WHEN the transition starts
+ progressListener.onTransitionStarted()
+ progressListener.onTransitionProgress(0f)
+
+ list.forEach { (view, direction) ->
+ assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
+ }
+
+ // WHEN the transition progresses, translation is updated
+ progressListener.onTransitionProgress(.5f)
+ list.forEach { (view, direction) ->
+ assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
+ }
+
+ // WHEN the transition ends, translation is completed
+ progressListener.onTransitionProgress(1f)
+ progressListener.onTransitionFinished()
+ list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
+ }
+
+ companion object {
+ private val LEFT = Direction.LEFT.multiplier.toInt()
+ private val RIGHT = Direction.RIGHT.multiplier.toInt()
+
+ private const val MAX_TRANSLATION = 42f
+
+ private const val LEFT_VIEW_ID = 1
+ private const val RIGHT_VIEW_ID = 2
+ }
+}
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/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 42647f7..9076e16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -169,8 +169,6 @@
transitionController.goToLockedShade(null)
whenever(statusbarStateController.state).thenReturn(StatusBarState.SHADE)
transitionController.goToLockedShade(null)
- whenever(statusbarStateController.state).thenReturn(StatusBarState.FULLSCREEN_USER_SWITCHER)
- transitionController.goToLockedShade(null)
verify(statusbarStateController, never()).setState(anyInt())
}
@@ -227,7 +225,7 @@
fun testDragDownAmountDoesntCallOutInLockedDownShade() {
whenever(nsslController.isInLockedDownShade).thenReturn(true)
transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -238,7 +236,7 @@
@Test
fun testDragDownAmountCallsOut() {
transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 83f1d87..7fafb24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -56,6 +57,7 @@
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.bubbles.Bubbles;
import com.google.android.collect.Lists;
@@ -123,7 +125,9 @@
mock(DynamicChildBindController.class),
mock(LowPriorityInflationHelper.class),
mock(AssistantFeedbackController.class),
- mNotifPipelineFlags);
+ mNotifPipelineFlags,
+ mock(KeyguardUpdateMonitor.class),
+ mock(KeyguardStateController.class));
mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
deleted file mode 100644
index c7c8d04..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.IForegroundServiceObserver;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
-import android.util.Pair;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.test.filters.MediumTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.RunningFgsController;
-import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime;
-import com.android.systemui.statusbar.policy.RunningFgsControllerImpl;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Random;
-import java.util.function.Consumer;
-
-@MediumTest
-@RunWith(AndroidTestingRunner.class)
-public class RunningFgsControllerTest extends SysuiTestCase {
-
- private RunningFgsController mController;
-
- private FakeSystemClock mSystemClock = new FakeSystemClock();
- private FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
- private TestCallback mCallback = new TestCallback();
-
- @Mock
- private IActivityManager mActivityManager;
- @Mock
- private Lifecycle mLifecycle;
- @Mock
- private LifecycleOwner mLifecycleOwner;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
- mController = new RunningFgsControllerImpl(mExecutor, mSystemClock, mActivityManager);
- }
-
- @Test
- public void testInitRegistersListenerInImpl() throws RemoteException {
- ((RunningFgsControllerImpl) mController).init();
- verify(mActivityManager, times(1)).registerForegroundServiceObserver(any());
- }
-
- @Test
- public void testAddCallbackCallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.addCallback(mCallback));
- }
-
- @Test
- public void testRemoveCallbackCallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.removeCallback(mCallback));
- }
-
- @Test
- public void testObserve1CallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.observe(mLifecycle, mCallback));
- }
-
- @Test
- public void testObserve2CallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.observe(mLifecycleOwner, mCallback));
- }
-
- @Test
- public void testGetPackagesWithFgsCallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.getPackagesWithFgs());
- }
-
- @Test
- public void testStopFgsCallsInitInImpl() {
- verifyInitIsCalled(controller -> controller.stopFgs(0, ""));
- }
-
- /**
- * Tests that callbacks can be added
- */
- @Test
- public void testAddCallback() throws RemoteException {
- String testPackageName = "testPackageName";
- int testUserId = 0;
-
- IForegroundServiceObserver observer = prepareObserver();
- mController.addCallback(mCallback);
-
- observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true);
-
- mExecutor.advanceClockToLast();
- mExecutor.runAllReady();
-
- assertEquals("Callback should have been invoked exactly once.",
- 1, mCallback.mInvocations.size());
-
- List<UserPackageTime> userPackageTimes = mCallback.mInvocations.get(0);
- assertEquals("There should have only been one package in callback. packages="
- + userPackageTimes,
- 1, userPackageTimes.size());
-
- UserPackageTime upt = userPackageTimes.get(0);
- assertEquals(testPackageName, upt.getPackageName());
- assertEquals(testUserId, upt.getUserId());
- }
-
- /**
- * Tests that callbacks can be removed. This test is only meaningful if
- * {@link #testAddCallback()} can pass.
- */
- @Test
- public void testRemoveCallback() throws RemoteException {
- String testPackageName = "testPackageName";
- int testUserId = 0;
-
- IForegroundServiceObserver observer = prepareObserver();
- mController.addCallback(mCallback);
- mController.removeCallback(mCallback);
-
- observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true);
-
- mExecutor.advanceClockToLast();
- mExecutor.runAllReady();
-
- assertEquals("Callback should not have been invoked.",
- 0, mCallback.mInvocations.size());
- }
-
- /**
- * Tests packages are added when the controller receives a callback from activity manager for
- * a foreground service start.
- */
- @Test
- public void testGetPackagesWithFgsAddingPackages() throws RemoteException {
- int numPackages = 20;
- int numUsers = 3;
-
- IForegroundServiceObserver observer = prepareObserver();
-
- assertEquals("List should be empty", 0, mController.getPackagesWithFgs().size());
-
- List<Pair<Integer, String>> addedPackages = new ArrayList<>();
- for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) {
- for (int userId = 0; userId < numUsers; userId++) {
- String packageName = "package.name." + pkgNumber;
- addedPackages.add(new Pair(userId, packageName));
-
- observer.onForegroundStateChanged(new Binder(), packageName, userId, true);
-
- containsAllAddedPackages(addedPackages, mController.getPackagesWithFgs());
- }
- }
- }
-
- /**
- * Tests packages are removed when the controller receives a callback from activity manager for
- * a foreground service ending.
- */
- @Test
- public void testGetPackagesWithFgsRemovingPackages() throws RemoteException {
- int numPackages = 20;
- int numUsers = 3;
- int arrayLength = numPackages * numUsers;
-
- String[] packages = new String[arrayLength];
- int[] users = new int[arrayLength];
- IBinder[] tokens = new IBinder[arrayLength];
- for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) {
- for (int userId = 0; userId < numUsers; userId++) {
- int i = pkgNumber * numUsers + userId;
- packages[i] = "package.name." + pkgNumber;
- users[i] = userId;
- tokens[i] = new Binder();
- }
- }
-
- IForegroundServiceObserver observer = prepareObserver();
-
- for (int i = 0; i < packages.length; i++) {
- observer.onForegroundStateChanged(tokens[i], packages[i], users[i], true);
- }
-
- assertEquals(packages.length, mController.getPackagesWithFgs().size());
-
- List<Integer> removeOrder = new ArrayList<>();
- for (int i = 0; i < packages.length; i++) {
- removeOrder.add(i);
- }
- Collections.shuffle(removeOrder, new Random(12345));
-
- for (int idx : removeOrder) {
- removePackageAndAssertRemovedFromList(observer, tokens[idx], packages[idx], users[idx]);
- }
-
- assertEquals(0, mController.getPackagesWithFgs().size());
- }
-
- /**
- * Tests a call on stopFgs forwards to activity manager.
- */
- @Test
- public void testStopFgs() throws RemoteException {
- String pkgName = "package.name";
- mController.stopFgs(0, pkgName);
- verify(mActivityManager).makeServicesNonForeground(pkgName, 0);
- }
-
- /**
- * Tests a package which starts multiple services is only listed once and is only removed once
- * all services are stopped.
- */
- @Test
- public void testSinglePackageWithMultipleServices() throws RemoteException {
- String packageName = "package.name";
- int userId = 0;
- IBinder serviceToken1 = new Binder();
- IBinder serviceToken2 = new Binder();
-
- IForegroundServiceObserver observer = prepareObserver();
-
- assertEquals(0, mController.getPackagesWithFgs().size());
-
- observer.onForegroundStateChanged(serviceToken1, packageName, userId, true);
- assertSinglePackage(packageName, userId);
-
- observer.onForegroundStateChanged(serviceToken2, packageName, userId, true);
- assertSinglePackage(packageName, userId);
-
- observer.onForegroundStateChanged(serviceToken2, packageName, userId, false);
- assertSinglePackage(packageName, userId);
-
- observer.onForegroundStateChanged(serviceToken1, packageName, userId, false);
- assertEquals(0, mController.getPackagesWithFgs().size());
- }
-
- private IForegroundServiceObserver prepareObserver()
- throws RemoteException {
- mController.getPackagesWithFgs();
-
- ArgumentCaptor<IForegroundServiceObserver> argumentCaptor =
- ArgumentCaptor.forClass(IForegroundServiceObserver.class);
- verify(mActivityManager).registerForegroundServiceObserver(argumentCaptor.capture());
-
- return argumentCaptor.getValue();
- }
-
- private void verifyInitIsCalled(Consumer<RunningFgsControllerImpl> c) {
- RunningFgsControllerImpl spiedController = Mockito.spy(
- ((RunningFgsControllerImpl) mController));
- c.accept(spiedController);
- verify(spiedController, atLeastOnce()).init();
- }
-
- private void containsAllAddedPackages(List<Pair<Integer, String>> addedPackages,
- List<UserPackageTime> runningFgsPackages) {
- for (Pair<Integer, String> userPkg : addedPackages) {
- assertTrue(userPkg + " was not found in returned list",
- runningFgsPackages.stream().anyMatch(
- upt -> userPkg.first == upt.getUserId()
- && Objects.equals(upt.getPackageName(), userPkg.second)));
- }
- for (UserPackageTime upt : runningFgsPackages) {
- int userId = upt.getUserId();
- String packageName = upt.getPackageName();
- assertTrue("Unknown <user=" + userId + ", package=" + packageName + ">"
- + " in returned list",
- addedPackages.stream().anyMatch(userPkg -> userPkg.first == userId
- && Objects.equals(packageName, userPkg.second)));
- }
- }
-
- private void removePackageAndAssertRemovedFromList(IForegroundServiceObserver observer,
- IBinder token, String pkg, int userId) throws RemoteException {
- observer.onForegroundStateChanged(token, pkg, userId, false);
- List<UserPackageTime> packagesWithFgs = mController.getPackagesWithFgs();
- assertFalse("Package \"" + pkg + "\" was not removed",
- packagesWithFgs.stream().anyMatch(upt ->
- Objects.equals(upt.getPackageName(), pkg) && upt.getUserId() == userId));
- }
-
- private void assertSinglePackage(String packageName, int userId) {
- assertEquals(1, mController.getPackagesWithFgs().size());
- assertEquals(packageName, mController.getPackagesWithFgs().get(0).getPackageName());
- assertEquals(userId, mController.getPackagesWithFgs().get(0).getUserId());
- }
-
- private static class TestCallback implements RunningFgsController.Callback {
-
- private List<List<UserPackageTime>> mInvocations = new ArrayList<>();
-
- @Override
- public void onFgsPackagesChanged(List<UserPackageTime> packages) {
- mInvocations.add(packages);
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 85ea52b..d13451d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -37,6 +39,7 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -130,7 +133,12 @@
Icon icon = Icon.createWithBitmap(largeBitmap);
StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
icon, 0, 0, "");
- assertFalse(mIconView.set(largeIcon));
+ assertTrue(mIconView.set(largeIcon));
+
+ // The view should downscale the bitmap.
+ BitmapDrawable drawable = (BitmapDrawable) mIconView.getDrawable();
+ assertThat(drawable.getBitmap().getWidth()).isLessThan(1000);
+ assertThat(drawable.getBitmap().getHeight()).isLessThan(1000);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index b736f38..a5ea897 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -61,18 +61,16 @@
@Test
fun testChangeState_logged() {
TestableLooper.get(this).runWithLooper {
- controller.state = StatusBarState.FULLSCREEN_USER_SWITCHER
controller.state = StatusBarState.KEYGUARD
controller.state = StatusBarState.SHADE
controller.state = StatusBarState.SHADE_LOCKED
}
val logs = uiEventLogger.logs
- assertEquals(4, logs.size)
+ assertEquals(3, logs.size)
val ids = logs.map(UiEventLoggerFake.FakeUiEvent::eventId)
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER.id, ids[0])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[1])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[2])
- assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[3])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD.id, ids[0])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[1])
+ assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[2])
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
index b5b2f1f..79a2008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt
@@ -33,18 +33,16 @@
StatusBarStateEvent.STATUS_BAR_STATE_SHADE,
StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED,
StatusBarStateEvent.STATUS_BAR_STATE_KEYGUARD,
- StatusBarStateEvent.STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER,
StatusBarStateEvent.STATUS_BAR_STATE_UNKNOWN
)
val states = listOf(
StatusBarState.SHADE,
StatusBarState.SHADE_LOCKED,
StatusBarState.KEYGUARD,
- StatusBarState.FULLSCREEN_USER_SWITCHER,
-1
)
events.zip(states).forEach { (event, state) ->
assertEquals(event, StatusBarStateEvent.fromState(state))
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
new file mode 100644
index 0000000..ad908e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.statusbar
+
+import android.media.AudioAttributes
+import android.os.UserHandle
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.Executor
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class VibratorHelperTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var vibrator: Vibrator
+ @Mock lateinit var executor: Executor
+ @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable>
+ lateinit var vibratorHelper: VibratorHelper
+
+ @Before
+ fun setup() {
+ vibratorHelper = VibratorHelper(vibrator, executor)
+ whenever(vibrator.hasVibrator()).thenReturn(true)
+ }
+
+ @Test
+ fun testVibrate() {
+ vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
+ verifyAsync().vibrate(any(VibrationEffect::class.java),
+ any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate2() {
+ vibratorHelper.vibrate(UserHandle.USER_CURRENT, "package",
+ mock(VibrationEffect::class.java), "reason",
+ mock(VibrationAttributes::class.java))
+ verifyAsync().vibrate(eq(UserHandle.USER_CURRENT), eq("package"),
+ any(VibrationEffect::class.java), eq("reason"),
+ any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate3() {
+ vibratorHelper.vibrate(mock(VibrationEffect::class.java), mock(AudioAttributes::class.java))
+ verifyAsync().vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java))
+ }
+
+ @Test
+ fun testVibrate4() {
+ vibratorHelper.vibrate(mock(VibrationEffect::class.java))
+ verifyAsync().vibrate(any(VibrationEffect::class.java))
+ }
+
+ @Test
+ fun testHasVibrator() {
+ assertThat(vibratorHelper.hasVibrator()).isTrue()
+ verify(vibrator).hasVibrator()
+ }
+
+ @Test
+ fun testCancel() {
+ vibratorHelper.cancel()
+ verifyAsync().cancel()
+ }
+
+ private fun verifyAsync(): Vibrator {
+ verify(executor).execute(backgroundTaskCaptor.capture())
+ verify(vibrator).hasVibrator()
+ backgroundTaskCaptor.value.run()
+
+ return verify(vibrator)
+ }
+}
\ No newline at end of file
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..cb248b0 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
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
@@ -46,6 +47,7 @@
import android.util.ArrayMap;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -112,7 +114,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;
@@ -368,6 +370,50 @@
}
@Test
+ public void testGroupsWhoLoseAllChildrenToPromotionSuppressSummary() {
+ // GIVEN a group with two children
+ addGroupChild(0, PACKAGE_2, GROUP_1);
+ addGroupSummary(1, PACKAGE_2, GROUP_1);
+ addGroupChild(2, PACKAGE_2, GROUP_1);
+
+ // GIVEN a promoter that will promote one of children to top level
+ mListBuilder.addPromoter(new IdPromoter(0, 2));
+
+ // WHEN we build the list
+ dispatchBuild();
+
+ // THEN both children end up at top level (because group is now too small)
+ verifyBuiltList(
+ notif(0),
+ notif(2)
+ );
+
+ // THEN the summary is discarded
+ assertNull(mEntrySet.get(1).getParent());
+ }
+
+ @Test
+ public void testGroupsWhoLoseOnlyChildToPromotionSuppressSummary() {
+ // GIVEN a group with two children
+ addGroupChild(0, PACKAGE_2, GROUP_1);
+ addGroupSummary(1, PACKAGE_2, GROUP_1);
+
+ // GIVEN a promoter that will promote one of children to top level
+ mListBuilder.addPromoter(new IdPromoter(0));
+
+ // WHEN we build the list
+ dispatchBuild();
+
+ // THEN both children end up at top level (because group is now too small)
+ verifyBuiltList(
+ notif(0)
+ );
+
+ // THEN the summary is discarded
+ assertNull(mEntrySet.get(1).getParent());
+ }
+
+ @Test
public void testPreviousParentsAreSetProperly() {
// GIVEN a notification that is initially added to the list
PackageFilter filter = new PackageFilter(PACKAGE_2);
@@ -828,6 +874,73 @@
}
@Test
+ public void testThatSectionComparatorsAreCalled() {
+ // GIVEN a section with a comparator that elevates some packages over others
+ NotifComparator comparator = spy(new HypeComparator(PACKAGE_2, PACKAGE_4));
+ NotifSectioner sectioner = new PackageSectioner(
+ List.of(PACKAGE_1, PACKAGE_2, PACKAGE_4, PACKAGE_5), comparator);
+ mListBuilder.setSectioners(List.of(sectioner));
+
+ // WHEN the pipeline is kicked off on a bunch of notifications
+ addNotif(0, PACKAGE_0);
+ addNotif(1, PACKAGE_1);
+ addNotif(2, PACKAGE_2);
+ addNotif(3, PACKAGE_3);
+ addNotif(4, PACKAGE_4);
+ addNotif(5, PACKAGE_5);
+ dispatchBuild();
+
+ // THEN the notifs are sorted according to both sectioning and the section's comparator
+ verifyBuiltList(
+ notif(2),
+ notif(4),
+ notif(1),
+ notif(5),
+ notif(0),
+ notif(3)
+ );
+
+ // VERIFY that the comparator is invoked at least 3 times
+ verify(comparator, atLeast(3)).compare(any(), any());
+
+ // VERIFY that the comparator is never invoked with the entry from package 0 or 3.
+ final NotificationEntry package0Entry = mEntrySet.get(0);
+ verify(comparator, never()).compare(eq(package0Entry), any());
+ verify(comparator, never()).compare(any(), eq(package0Entry));
+ final NotificationEntry package3Entry = mEntrySet.get(3);
+ verify(comparator, never()).compare(eq(package3Entry), any());
+ verify(comparator, never()).compare(any(), eq(package3Entry));
+ }
+
+ @Test
+ public void testThatSectionComparatorsAreNotCalledForSectionWithSingleEntry() {
+ // GIVEN a section with a comparator that will have only 1 element
+ NotifComparator comparator = spy(new HypeComparator(PACKAGE_3));
+ NotifSectioner sectioner = new PackageSectioner(List.of(PACKAGE_3), comparator);
+ mListBuilder.setSectioners(List.of(sectioner));
+
+ // WHEN the pipeline is kicked off on a bunch of notifications
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ addNotif(2, PACKAGE_3);
+ addNotif(3, PACKAGE_4);
+ addNotif(4, PACKAGE_5);
+ dispatchBuild();
+
+ // THEN the notifs are sorted according to the sectioning
+ verifyBuiltList(
+ notif(2),
+ notif(0),
+ notif(1),
+ notif(3),
+ notif(4)
+ );
+
+ // VERIFY that the comparator is never invoked
+ verify(comparator, never()).compare(any(), any());
+ }
+
+ @Test
public void testListenersAndPluggablesAreFiredInOrder() {
// GIVEN a bunch of registered listeners and pluggables
NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
@@ -890,7 +1003,8 @@
// GIVEN a variety of pluggables
NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
NotifPromoter idPromoter = new IdPromoter(4);
- NotifSectioner section = new PackageSectioner(PACKAGE_1);
+ NotifComparator sectionComparator = new HypeComparator(PACKAGE_1);
+ NotifSectioner section = new PackageSectioner(List.of(PACKAGE_1), sectionComparator);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
Invalidator preRenderInvalidator = new Invalidator("PreRenderInvalidator") {};
@@ -925,6 +1039,10 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
+ sectionComparator.invalidateList();
+ verify(mOnRenderListListener).onRenderList(anyList());
+
+ clearInvocations(mOnRenderListListener);
preRenderInvalidator.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -1642,6 +1760,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));
@@ -1980,16 +2111,30 @@
/** Represents a section for the passed pkg */
private static class PackageSectioner extends NotifSectioner {
- private final String mPackage;
+ private final List<String> mPackages;
+ private final NotifComparator mComparator;
+
+ PackageSectioner(List<String> pkgs, NotifComparator comparator) {
+ super("PackageSection_" + pkgs, 0);
+ mPackages = pkgs;
+ mComparator = comparator;
+ }
PackageSectioner(String pkg) {
super("PackageSection_" + pkg, 0);
- mPackage = pkg;
+ mPackages = List.of(pkg);
+ mComparator = null;
+ }
+
+ @Nullable
+ @Override
+ public NotifComparator getComparator() {
+ return mComparator;
}
@Override
public boolean isInSection(ListEntry entry) {
- return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
+ return mPackages.contains(entry.getRepresentativeEntry().getSbn().getPackageName());
}
}
@@ -2037,6 +2182,7 @@
}
private static class TestableStabilityManager extends NotifStabilityManager {
+ boolean mAllowPipelineRun = true;
boolean mAllowGroupChanges = true;
boolean mAllowSectionChanges = true;
boolean mAllowEntryReodering = true;
@@ -2060,6 +2206,15 @@
return this;
}
+ TestableStabilityManager setAllowPipelineRun(boolean allowPipelineRun) {
+ mAllowPipelineRun = allowPipelineRun;
+ return this;
+ }
+
+ @Override
+ public boolean isPipelineRunAllowed() {
+ return mAllowPipelineRun;
+ }
@Override
public void onBeginRun() {
@@ -2090,6 +2245,7 @@
}
}
+ private static final String PACKAGE_0 = "com.test0";
private static final String PACKAGE_1 = "com.test1";
private static final String PACKAGE_2 = "com.test2";
private static final String PACKAGE_3 = "org.test3";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 8deac94..7692a05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
@@ -41,7 +40,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@@ -79,7 +77,7 @@
}
peopleSectioner = coordinator.sectioner
- peopleComparator = coordinator.comparator
+ peopleComparator = peopleSectioner.comparator!!
entry = NotificationEntryBuilder().setChannel(channel).build()
@@ -108,16 +106,6 @@
}
@Test
- fun testComparatorIgnoresFromOtherSection() {
- val e1 = NotificationEntryBuilder().setId(1).setChannel(channel).build()
- val e2 = NotificationEntryBuilder().setId(2).setChannel(channel).build()
-
- // wrong section -- never classify
- assertThat(peopleComparator.compare(e1, e2)).isEqualTo(0)
- verify(peopleNotificationIdentifier, never()).getPeopleNotificationType(any())
- }
-
- @Test
fun testComparatorPutsImportantPeopleFirst() {
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
.thenReturn(TYPE_IMPORTANT_PERSON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index c67a233..144eefb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -15,14 +15,20 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.Notification.GROUP_ALERT_ALL
+import android.app.Notification.GROUP_ALERT_SUMMARY
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -32,6 +38,7 @@
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
+import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.FakeExecutor
@@ -64,10 +71,13 @@
private lateinit var mCollectionListener: NotifCollectionListener
private lateinit var mNotifPromoter: NotifPromoter
private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender
+ private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener
+ private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
private lateinit var mNotifSectioner: NotifSectioner
private val mNotifPipeline: NotifPipeline = mock()
+ private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
private val mHeadsUpManager: HeadsUpManager = mock()
private val mHeadsUpViewBinder: HeadsUpViewBinder = mock()
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
@@ -76,12 +86,24 @@
private val mHeaderController: NodeController = mock()
private lateinit var mEntry: NotificationEntry
- private val mExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var mGroupSummary: NotificationEntry
+ private lateinit var mGroupPriority: NotificationEntry
+ private lateinit var mGroupSibling1: NotificationEntry
+ private lateinit var mGroupSibling2: NotificationEntry
+ private lateinit var mGroupChild1: NotificationEntry
+ private lateinit var mGroupChild2: NotificationEntry
+ private lateinit var mGroupChild3: NotificationEntry
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
private val mHuns: ArrayList<NotificationEntry> = ArrayList()
+ private lateinit var mHelper: NotificationGroupTestHelper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ mHelper = NotificationGroupTestHelper(mContext)
mCoordinator = HeadsUpCoordinator(
+ mLogger,
+ mSystemClock,
mHeadsUpManager,
mHeadsUpViewBinder,
mNotificationInterruptStateProvider,
@@ -100,6 +122,12 @@
mNotifLifetimeExtender = withArgCaptor {
verify(mNotifPipeline).addNotificationLifetimeExtender(capture())
}
+ mBeforeTransformGroupsListener = withArgCaptor {
+ verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture())
+ }
+ mBeforeFinalizeFilterListener = withArgCaptor {
+ verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
mOnHeadsUpChangedListener = withArgCaptor {
verify(mHeadsUpManager).addListener(capture())
}
@@ -116,6 +144,16 @@
mNotifSectioner = mCoordinator.sectioner
mNotifLifetimeExtender.setCallback(mEndLifetimeExtension)
mEntry = NotificationEntryBuilder().build()
+ // Same summary we can use for either set of children
+ mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500)
+ // One set of children with GROUP_ALERT_SUMMARY
+ mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400)
+ mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300)
+ mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200)
+ // Another set of children with GROUP_ALERT_ALL
+ mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350)
+ mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
+ mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)
}
@Test
@@ -156,6 +194,34 @@
}
@Test
+ fun testPromotesAddedHUN() {
+ // GIVEN the current entry should heads up
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+ // WHEN the notification is added but not yet binding
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
+
+ // THEN only promote mEntry
+ assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ }
+
+ @Test
+ fun testPromotesBindingHUN() {
+ // GIVEN the current entry should heads up
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+ // WHEN the notification started binding on the previous run
+ mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any())
+
+ // THEN only promote mEntry
+ assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+ }
+
+ @Test
fun testPromotesCurrentHUN() {
// GIVEN the current HUN is set to mEntry
addHUN(mEntry)
@@ -198,6 +264,9 @@
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+ verify(mHeadsUpManager, never()).showNotification(mEntry)
withArgCaptor<BindCallback> {
verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture())
}.onBindFinished(mEntry)
@@ -211,6 +280,8 @@
// WHEN a notification shouldn't HUN and its inflation is finished
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false)
mCollectionListener.onEntryAdded(mEntry)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
// THEN we never bind the heads up view or tell HeadsUpManager to show the notification
verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
@@ -235,4 +306,296 @@
whenever(mHeadsUpManager.topEntry).thenReturn(entry)
mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testTransferIsolatedChildAlert_withGroupAlertSummary() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupSibling1)
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupSibling1)
+ }
+
+ @Test
+ fun testTransferIsolatedChildAlert_withGroupAlertAll() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ }
+
+ @Test
+ fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+ val entryList = listOf(mGroupSibling1, mGroupSibling2)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupSibling1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val entryList = listOf(mGroupChild1, mGroupChild2)
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList)
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupChild1)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnAddWithTwoSiblings() {
+ // WHEN a notification should HUN and its inflation is finished
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupPriority)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ // THEN we tell the HeadsUpManager to show the notification
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnUpdateWithTwoSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupPriority)
+ mCollectionListener.onEntryUpdated(mGroupSibling1)
+ mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupPriority)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+ finishBind(mGroupPriority)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+ verify(mHeadsUpManager).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferToPriorityOnUpdateOfTwoSiblings() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority))
+
+ mCollectionListener.onEntryUpdated(mGroupSummary)
+ mCollectionListener.onEntryUpdated(mGroupSibling1)
+ mCollectionListener.onEntryUpdated(mGroupSibling2)
+
+ val beforeTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+
+ val afterTransformGroup = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeFinalizeFilterListener
+ .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupPriority)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferTwoChildAlert_withGroupAlertSummary() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupSibling1)
+ mCollectionListener.onEntryAdded(mGroupSibling2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupSibling1, mGroupSibling2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+ }
+
+ @Test
+ fun testNoTransferTwoChildAlert_withGroupAlertAll() {
+ setShouldHeadsUp(mGroupSummary)
+ whenever(mNotifPipeline.allNotifs)
+ .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2))
+
+ mCollectionListener.onEntryAdded(mGroupSummary)
+ mCollectionListener.onEntryAdded(mGroupChild1)
+ mCollectionListener.onEntryAdded(mGroupChild2)
+ val groupEntry = GroupEntryBuilder()
+ .setSummary(mGroupSummary)
+ .setChildren(listOf(mGroupChild1, mGroupChild2))
+ .build()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ finishBind(mGroupSummary)
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any())
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+ verify(mHeadsUpManager).showNotification(mGroupSummary)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild1)
+ verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+ }
+
+ private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
+ whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
+ }
+
+ private fun finishBind(entry: NotificationEntry) {
+ verify(mHeadsUpManager, never()).showNotification(entry)
+ withArgCaptor<BindCallback> {
+ verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+ }.onBindFinished(entry)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 5fd4174..3f84c16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -19,8 +19,11 @@
import android.os.UserHandle
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -28,9 +31,12 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Test
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -40,9 +46,13 @@
val dynamicPrivacyController: DynamicPrivacyController = mock()
val lockscreenUserManager: NotificationLockscreenUserManager = mock()
val pipeline: NotifPipeline = mock()
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
+ val statusBarStateController: StatusBarStateController = mock()
+ val keyguardStateController: KeyguardStateController = mock()
val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
- .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+ .provideCoordinator(dynamicPrivacyController, lockscreenUserManager,
+ keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -190,6 +200,28 @@
verify(entry.representativeEntry!!).setSensitive(true, true)
}
+ @Test
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
+ whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
+ .thenReturn(true)
+
+ val entry = fakeNotification(2, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!, never()).setSensitive(any(), any())
+ }
+
private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
val mockUserHandle = mock<UserHandle>().apply {
whenever(identifier).thenReturn(notifUserId)
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/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index 15ff555..ab71264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.render;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.content.Context;
import android.testing.AndroidTestingRunner;
@@ -138,7 +139,7 @@
}
@Test
- public void testRemovedGroupsAreKeptTogether() {
+ public void testRemovedGroupsAreBrokenApart() {
// GIVEN a preexisting tree with a group
applySpecAndCheck(
node(mController1),
@@ -154,10 +155,10 @@
node(mController1)
);
- // THEN the group children are still attached to their parent
- assertEquals(mController2.getView(), mController3.getView().getParent());
- assertEquals(mController2.getView(), mController4.getView().getParent());
- assertEquals(mController2.getView(), mController5.getView().getParent());
+ // THEN the group children are no longer attached to their parent
+ assertNull(mController3.getView().getParent());
+ assertNull(mController4.getView().getParent());
+ assertNull(mController5.getView().getParent());
}
@Test
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/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
new file mode 100644
index 0000000..0909ff2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.R
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link MediaContainView}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaContainerViewTest : SysuiTestCase() {
+
+ lateinit var mediaContainerView : MediaContainerView
+
+ @Before
+ fun setUp() {
+ mediaContainerView = LayoutInflater.from(context).inflate(
+ R.layout.keyguard_media_container, null, false) as MediaContainerView
+ }
+
+ @Test
+ fun testUpdateClipping_updatesClipHeight() {
+ assertTrue(mediaContainerView.clipHeight == 0)
+
+ mediaContainerView.actualHeight = 10
+ mediaContainerView.updateClipping()
+ assertTrue(mediaContainerView.clipHeight == 10)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
new file mode 100644
index 0000000..d280f54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -0,0 +1,153 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationShelf
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.*
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for {@link NotificationShelf}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfTest : SysuiTestCase() {
+
+ private val shelf = NotificationShelf(context, /* attrs */ null)
+ private val shelfState = shelf.viewState as NotificationShelf.ShelfState
+ private val ambientState = mock(AmbientState::class.java)
+
+ @Before
+ fun setUp() {
+ shelf.bind(ambientState, /* hostLayoutController */ null)
+ shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ }
+
+ @Test
+ fun testShadeWidth_BasedOnFractionToShade() {
+ setFractionToShade(0f)
+ setOnLockscreen(true)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 10)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 20)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testShelfIsLong_WhenNotOnLockscreen() {
+ setFractionToShade(0f)
+ setOnLockscreen(false)
+
+ shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10)
+ assertTrue(shelfState.actualWidth == 30)
+ }
+
+ @Test
+ fun testX_inViewForClick() {
+ val isXInView = shelf.isXInView(
+ /* localX */ 5f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isXInView)
+ }
+
+ @Test
+ fun testXSlop_inViewForClick() {
+ val isLeftXSlopInView = shelf.isXInView(
+ /* localX */ -3f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isLeftXSlopInView)
+
+ val isRightXSlopInView = shelf.isXInView(
+ /* localX */ 13f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertTrue(isRightXSlopInView)
+ }
+
+ @Test
+ fun testX_notInViewForClick() {
+ val isXLeftOfShelfInView = shelf.isXInView(
+ /* localX */ -10f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXLeftOfShelfInView)
+
+ val isXRightOfShelfInView = shelf.isXInView(
+ /* localX */ 20f,
+ /* slop */ 5f,
+ /* left */ 0f,
+ /* right */ 10f)
+ assertFalse(isXRightOfShelfInView)
+ }
+
+ @Test
+ fun testY_inViewForClick() {
+ val isYInView = shelf.isYInView(
+ /* localY */ 5f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isYInView)
+ }
+
+ @Test
+ fun testYSlop_inViewForClick() {
+ val isTopYSlopInView = shelf.isYInView(
+ /* localY */ -3f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isTopYSlopInView)
+
+ val isBottomYSlopInView = shelf.isYInView(
+ /* localY */ 13f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 10f)
+ assertTrue(isBottomYSlopInView)
+ }
+
+ @Test
+ fun testY_notInViewForClick() {
+ val isYAboveShelfInView = shelf.isYInView(
+ /* localY */ -10f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYAboveShelfInView)
+
+ val isYBelowShelfInView = shelf.isYInView(
+ /* localY */ 15f,
+ /* slop */ 5f,
+ /* top */ 0f,
+ /* bottom */ 5f)
+ assertFalse(isYBelowShelfInView)
+ }
+
+ private fun setFractionToShade(fraction: Float) {
+ shelf.setFractionToShade(fraction)
+ }
+
+ private fun setOnLockscreen(isOnLockscreen: Boolean) {
+ whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen)
+ }
+}
\ No newline at end of file
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/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 46ba097..4f731ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack;
-import static android.provider.Settings.Secure.NOTIFICATION_HISTORY_ENABLED;
import static android.view.View.GONE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
@@ -27,6 +26,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
@@ -40,10 +40,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.MathUtils;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -110,10 +109,6 @@
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
- Settings.Secure.putIntForUser(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED,
- 1, UserHandle.USER_CURRENT);
-
-
// Interact with real instance of AmbientState.
mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController);
@@ -148,6 +143,7 @@
mStackScroller.setShelfController(notificationShelfController);
mStackScroller.setStatusBar(mBar);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
+ when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNoticationRoundessManager())
.thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController);
@@ -165,6 +161,47 @@
}
@Test
+ public void testUpdateStackEndHeight_forEndOfStackHeightAnimation() {
+ final float nsslHeight = 10f;
+ final float bottomMargin = 1f;
+ final float topPadding = 1f;
+
+ mStackScroller.updateStackEndHeight(nsslHeight, bottomMargin, topPadding);
+ final float stackEndHeight = nsslHeight - bottomMargin - topPadding;
+ assertTrue(mAmbientState.getStackEndHeight() == stackEndHeight);
+ }
+
+ @Test
+ public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
+ final float dozeAmount = 0.5f;
+ mAmbientState.setDozeAmount(dozeAmount);
+
+ final float endHeight = 8f;
+ final float expansionFraction = 1f;
+ float expected = MathUtils.lerp(
+ endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, dozeAmount);
+
+ mStackScroller.updateStackHeight(endHeight, expansionFraction);
+ assertTrue(mAmbientState.getStackHeight() == expected);
+ }
+
+ @Test
+ public void testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging() {
+ final float dozeAmount = 1f;
+ mAmbientState.setDozeAmount(dozeAmount);
+
+ final float endHeight = 8f;
+ final float expansionFraction = 0.5f;
+ final float expected = MathUtils.lerp(
+ endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, expansionFraction);
+
+ mStackScroller.updateStackHeight(endHeight, expansionFraction);
+ assertTrue(mAmbientState.getStackHeight() == expected);
+ }
+
+ @Test
public void testNotDimmedOnKeyguard() {
when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
@@ -361,6 +398,22 @@
}
@Test
+ public void testUpdateFooter_withoutHistory() {
+ setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
+
+ when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false);
+ when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+ when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
+ .thenReturn(true);
+
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
+ mStackScroller.updateFooter();
+ verify(mStackScroller).updateFooterView(true, true, false);
+ }
+
+ @Test
public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ea68143..1da9bbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -69,10 +69,9 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- val closeHandleUnderlapHeight =
- context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap)
- val fullHeight =
- ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY
+ val marginBottom =
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5ca1f21..fb232ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -38,6 +38,7 @@
import android.testing.TestableResources;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -46,6 +47,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -107,6 +109,10 @@
private StatusBarStateController mStatusBarStateController;
@Mock
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ @Mock
+ private SessionTracker mSessionTracker;
+ @Mock
+ private LatencyTracker mLatencyTracker;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -129,7 +135,8 @@
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
- mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController);
+ mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
+ mSessionTracker, mLatencyTracker);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 36a4c1e..01e9822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -23,9 +23,13 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -45,6 +49,7 @@
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -52,6 +57,7 @@
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;
@@ -87,6 +93,12 @@
private SysuiStatusBarStateController mStatusBarStateController;
@Mock
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
+ @Mock
+ private UserManager mUserManager;
+ @Captor
+ private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
+ @Captor
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -101,8 +113,8 @@
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
mKeyguardStatusBarView =
- (KeyguardStatusBarView) LayoutInflater.from(mContext)
- .inflate(R.layout.keyguard_status_bar, null);
+ spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
+ .inflate(R.layout.keyguard_status_bar, null));
});
mController = new KeyguardStatusBarViewController(
@@ -121,7 +133,8 @@
mKeyguardUpdateMonitor,
mBiometricUnlockController,
mStatusBarStateController,
- mStatusBarContentInsetsProvider
+ mStatusBarContentInsetsProvider,
+ mUserManager
);
}
@@ -133,6 +146,31 @@
verify(mAnimationScheduler).addCallback(any());
verify(mUserInfoController).addCallback(any());
verify(mStatusBarIconController).addIconGroup(any());
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 5d7b154..d002ceb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -250,15 +250,17 @@
@Notification.GroupAlertBehavior int priorityGroupAlert,
@Notification.GroupAlertBehavior int siblingGroupAlert,
boolean expectAlertOverride) {
+ long when = 10000;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling");
+ siblings[i] = mGroupTestHelper
+ .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority");
+ mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary");
+ mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
@@ -322,17 +324,19 @@
@Test
public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
+ long when = 10000;
int numSiblings = 2;
int groupAlert = Notification.GROUP_ALERT_SUMMARY;
// Create entries in an order so that the priority entry can be deemed the newest child.
NotificationEntry[] siblings = new NotificationEntry[numSiblings];
for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling");
+ siblings[i] =
+ mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
}
NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(groupAlert, 0, "priority");
+ mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary");
+ mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
// The priority entry is an important conversation.
when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index ac32b9d..47f15a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -59,6 +59,13 @@
return createEntry(id, tag, true, groupAlertBehavior);
}
+ public NotificationEntry createSummaryNotification(
+ int groupAlertBehavior, int id, String tag, long when) {
+ NotificationEntry entry = createSummaryNotification(groupAlertBehavior, id, tag);
+ entry.getSbn().getNotification().when = when;
+ return entry;
+ }
+
public NotificationEntry createChildNotification() {
return createChildNotification(Notification.GROUP_ALERT_ALL);
}
@@ -71,6 +78,13 @@
return createEntry(id, tag, false, groupAlertBehavior);
}
+ public NotificationEntry createChildNotification(
+ int groupAlertBehavior, int id, String tag, long when) {
+ NotificationEntry entry = createChildNotification(groupAlertBehavior, id, tag);
+ entry.getSbn().getNotification().when = when;
+ return entry;
+ }
+
public NotificationEntry createEntry(int id, String tag, boolean isSummary,
int groupAlertBehavior) {
Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 1c8b35a..dee88db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -23,7 +23,6 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.reset;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +37,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -120,6 +120,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
@@ -365,6 +366,8 @@
private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ @Mock
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -490,7 +493,10 @@
.thenReturn(true);
when(mInteractionJankMonitor.end(anyInt()))
.thenReturn(true);
- reset(mView);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
mMainHandler = new Handler(Looper.getMainLooper());
@@ -505,6 +511,7 @@
mCommunalStateController, mKeyguardStateController,
mStatusBarStateController,
mStatusBarWindowStateController,
+ mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mCommunalSourceMonitor, mMetricsLogger, mActivityManager, mConfigurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 337e645..bbb2346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -6,6 +6,8 @@
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.recents.OverviewProxyService
@@ -46,6 +48,8 @@
private lateinit var overviewProxyService: OverviewProxyService
@Mock
private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@Captor
@@ -64,7 +68,8 @@
notificationsQSContainerController = NotificationsQSContainerController(
notificationsQSContainer,
navigationModeController,
- overviewProxyService
+ overviewProxyService,
+ featureFlags
)
whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
.thenReturn(NOTIFICATIONS_MARGIN)
@@ -85,6 +90,26 @@
@Test
fun testTaskbarVisibleInSplitShade() {
notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(false)
+
+ given(taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+
+ given(taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarVisibleInSplitShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(true)
+
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withStableBottom())
@@ -102,6 +127,26 @@
fun testTaskbarNotVisibleInSplitShade() {
// when taskbar is not visible, it means we're on the home screen
notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(false)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade_newFooter() {
+ // when taskbar is not visible, it means we're on the home screen
+ notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(true)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withStableBottom())
@@ -117,6 +162,25 @@
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(false)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout())
+ then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(true)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withCutout())
@@ -132,6 +196,24 @@
@Test
fun testTaskbarVisibleInSinglePaneShade() {
notificationsQSContainerController.splitShadeEnabled = false
+ useNewFooter(false)
+
+ given(taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ useNewFooter(true)
+
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withStableBottom())
@@ -146,6 +228,8 @@
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
notificationsQSContainerController.splitShadeEnabled = false
+ useNewFooter(false)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = emptyInsets())
@@ -159,14 +243,56 @@
given(taskbarVisible = false,
navigationMode = BUTTONS_NAVIGATION,
insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = STABLE_INSET_BOTTOM)
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ useNewFooter(true)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom())
+ then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
}
@Test
fun testCustomizingInSinglePaneShade() {
notificationsQSContainerController.splitShadeEnabled = false
notificationsQSContainerController.setCustomizerShowing(true)
+ useNewFooter(false)
+
+ // always sets spacings to 0
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testCustomizingInSinglePaneShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ notificationsQSContainerController.setCustomizerShowing(true)
+ useNewFooter(true)
+
// always sets spacings to 0
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -185,6 +311,28 @@
fun testDetailShowingInSinglePaneShade() {
notificationsQSContainerController.splitShadeEnabled = false
notificationsQSContainerController.setDetailShowing(true)
+ useNewFooter(false)
+
+ // always sets spacings to 0
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ notificationsQSContainerController.setDetailShowing(true)
+ useNewFooter(true)
+
// always sets spacings to 0
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -202,6 +350,8 @@
@Test
fun testDetailShowingInSplitShade() {
notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(false)
+
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = windowInsets().withStableBottom())
@@ -215,6 +365,36 @@
then(expectedContainerPadding = 0)
}
+ @Test
+ fun testDetailShowingInSplitShade_newFooter() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ useNewFooter(true)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ notificationsQSContainerController.setDetailShowing(true)
+ // should not influence spacing
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100)
+ notificationsQSContainerController.updateMargins()
+ notificationsQSContainerController.splitShadeEnabled = false
+
+ verify(notificationsQSContainer).setNotificationsMarginBottom(100)
+ }
+
private fun given(
taskbarVisible: Boolean,
navigationMode: Int,
@@ -234,7 +414,13 @@
verify(notificationsQSContainer)
.setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+ val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
+ if (newFooter) {
+ verify(notificationsQSContainer)
+ .setQSContainerPaddingBottom(expectedNotificationsMargin)
+ } else {
+ verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+ }
Mockito.clearInvocations(notificationsQSContainer)
}
@@ -251,4 +437,8 @@
whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
return this
}
+
+ private fun useNewFooter(useNewFooter: Boolean) {
+ whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index a5651f3..671ab59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -225,4 +226,17 @@
assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue();
assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
}
+
+ @Test
+ public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
+ mNotificationShadeWindowController.setForceDozeBrightness(true);
+ verify(mWindowManager).updateViewLayout(any(), any());
+
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mNotificationShadeWindowController.setForceDozeBrightness(false);
+ verify(mWindowManager, never()).updateViewLayout(any(), any());
+ });
+ verify(mWindowManager).updateViewLayout(any(), any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 5d80bca..107ba81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -36,6 +36,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
@@ -43,6 +44,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -97,6 +99,10 @@
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ShadeController mShadeController;
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+ @Mock
+ private LatencyTracker mLatencyTracker;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -116,6 +122,7 @@
mStatusBarStateController,
mock(ConfigurationController.class),
mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
mock(NavigationModeController.class),
mock(DockManager.class),
mock(NotificationShadeWindowController.class),
@@ -123,7 +130,8 @@
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
- () -> mShadeController);
+ () -> mShadeController,
+ mLatencyTracker);
mStatusBarKeyguardViewManager.registerStatusBar(
mStatusBar,
mNotificationPanelView,
@@ -167,17 +175,6 @@
}
@Test
- public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
- // TODO: StatusBar should not be here, mBouncer.isFullscreenBouncer() should do the same.
- when(mStatusBar.isFullScreenUserSwitcherState()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- /* fraction= */ 0.5f,
- /* expanded= */ false,
- /* tracking= */ true);
- verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
- }
-
- @Test
public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
when(mBouncer.isShowing()).thenReturn(true);
when(mBouncer.isScrimmed()).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..90b93e7 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
@@ -87,6 +87,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -285,6 +286,7 @@
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
+ @Mock private DreamOverlayStateController mDreamOverlayStateController;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -367,6 +369,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,
@@ -470,7 +476,8 @@
mActivityLaunchAnimator,
mNotifPipelineFlags,
mJankMonitor,
- mDeviceStateManager);
+ mDeviceStateManager,
+ mDreamOverlayStateController);
when(mKeyguardViewMediator.registerStatusBar(
any(StatusBar.class),
any(NotificationPanelViewController.class),
@@ -878,15 +885,6 @@
}
@Test
- public void testSetState_changesIsFullScreenUserSwitcherState() {
- mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
- assertFalse(mStatusBar.isFullScreenUserSwitcherState());
-
- mStatusBar.setBarStateForTest(StatusBarState.FULLSCREEN_USER_SWITCHER);
- assertTrue(mStatusBar.isFullScreenUserSwitcherState());
- }
-
- @Test
public void testShowKeyguardImplementation_setsState() {
when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
@@ -896,12 +894,6 @@
mStatusBar.showKeyguardImpl();
verify(mStatusBarStateController).setState(
eq(StatusBarState.KEYGUARD), eq(false) /* force */);
-
- // If useFullscreenUserSwitcher is true, state is set to FULLSCREEN_USER_SWITCHER.
- when(mUserSwitcherController.useFullscreenUserSwitcher()).thenReturn(true);
- mStatusBar.showKeyguardImpl();
- verify(mStatusBarStateController).setState(
- eq(StatusBarState.FULLSCREEN_USER_SWITCHER), eq(false) /* force */);
}
@Test
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/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index d325840..424a400 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -128,6 +128,28 @@
}
@Test
+ public void testCompareTo_withNullEntries() {
+ NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+ mHeadsUpManager.showNotification(alertEntry);
+
+ assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
+ assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
+ assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testCompareTo_withNonAlertEntries() {
+ NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
+ NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
+ NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+ mHeadsUpManager.showNotification(alertEntry);
+
+ assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+ assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+ assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+ }
+
+ @Test
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
ongoingCall.setEntry(new NotificationEntryBuilder()
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index fa2a906..9a7e702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -400,4 +400,16 @@
assertEquals(fgUserName, userSwitcherController.currentUserName)
}
+
+ @Test
+ fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
+ `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
+ assertEquals(true, userSwitcherController.isSystemUser)
+ }
+
+ @Test
+ fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
+ `when`(userTracker.userId).thenReturn(1)
+ assertEquals(false, userSwitcherController.isSystemUser)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
new file mode 100644
index 0000000..ac357ea
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.util.drawable
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DrawableSizeTest : SysuiTestCase() {
+
+ lateinit var resources: Resources
+
+ @Before
+ fun setUp() {
+ resources = context.resources
+ }
+
+ @Test
+ fun testDownscaleToSize_drawableZeroSize_unchanged() {
+ val drawable = ShapeDrawable()
+ val result = DrawableSize.downscaleToSize(resources, drawable, 100, 100)
+ assertThat(result).isSameInstanceAs(drawable)
+ }
+
+ @Test
+ fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() {
+ val drawable = BitmapDrawable(resources,
+ Bitmap.createBitmap(
+ resources.displayMetrics,
+ 150,
+ 150,
+ Bitmap.Config.ARGB_8888
+ )
+ )
+ val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300)
+ assertThat(result).isSameInstanceAs(drawable)
+ }
+
+ @Test
+ fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() {
+ // This bitmap would actually fail to resize if the method doesn't check for
+ // bitmap dimensions inside drawable.
+ val drawable = BitmapDrawable(resources,
+ Bitmap.createBitmap(
+ resources.displayMetrics,
+ 150,
+ 75,
+ Bitmap.Config.ARGB_8888
+ )
+ )
+
+ val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75)
+ assertThat(result).isNotSameInstanceAs(drawable)
+ assertThat(result.intrinsicWidth).isEqualTo(75)
+ assertThat(result.intrinsicHeight).isEqualTo(37)
+ }
+
+ @Test
+ fun testDownscaleToSize_drawableAnimated_unchanged() {
+ val drawable = resources.getDrawable(android.R.drawable.stat_sys_download,
+ resources.newTheme())
+ val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1)
+ assertThat(result).isSameInstanceAs(drawable)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
new file mode 100644
index 0000000..a2fd288
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PackageObserverTest extends SysuiTestCase {
+ @Mock
+ Context mContext;
+
+ @Mock
+ Observer.Callback mCallback;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testChange() {
+ final PackageObserver observer = new PackageObserver(mContext,
+ ComponentName.unflattenFromString("com.foo.bar/baz"));
+ final ArgumentCaptor<BroadcastReceiver> receiverCapture =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ observer.addCallback(mCallback);
+
+ // Verify broadcast receiver registered.
+ verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
+
+ // Simulate package change.
+ receiverCapture.getValue().onReceive(mContext, new Intent());
+
+ // Check that callback was informed.
+ verify(mCallback).onSourceChanged();
+
+ observer.removeCallback(mCallback);
+
+ // Make sure receiver is unregistered on last callback removal
+ verify(mContext).unregisterReceiver(receiverCapture.getValue());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
new file mode 100644
index 0000000..53d4a96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.service;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PersistentConnectionManagerTest extends SysuiTestCase {
+ private static final int MAX_RETRIES = 5;
+ private static final int RETRY_DELAY_MS = 1000;
+ private static final int CONNECTION_MIN_DURATION_MS = 5000;
+
+ private FakeSystemClock mFakeClock = new FakeSystemClock();
+ private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
+
+ @Mock
+ private ObservableServiceConnection<Proxy> mConnection;
+
+ @Mock
+ private Observer mObserver;
+
+ private static class Proxy {
+ }
+
+ private PersistentConnectionManager<Proxy> mConnectionManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mConnectionManager = new PersistentConnectionManager<>(
+ mFakeClock,
+ mFakeExecutor,
+ mConnection,
+ MAX_RETRIES,
+ RETRY_DELAY_MS,
+ CONNECTION_MIN_DURATION_MS,
+ mObserver);
+ }
+
+ private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend(
+ ObservableServiceConnection<Proxy> mConnection, Proxy proxy) {
+ ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+ ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+ verify(mConnection).bind();
+ Mockito.clearInvocations(mConnection);
+
+ final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue();
+ if (proxy != null) {
+ callback.onConnected(mConnection, proxy);
+ } else {
+ callback.onDisconnected(mConnection, 0);
+ }
+
+ return callback;
+ }
+
+ /**
+ * Validates initial connection.
+ */
+ @Test
+ public void testConnect() {
+ mConnectionManager.start();
+ captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+ }
+
+ /**
+ * Ensures reconnection on disconnect.
+ */
+ @Test
+ public void testRetryOnBindFailure() {
+ mConnectionManager.start();
+ ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+ ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+
+ // Verify attempts happen. Note that we account for the retries plus initial attempt, which
+ // is not scheduled.
+ for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
+ verify(mConnection).bind();
+ Mockito.clearInvocations(mConnection);
+ connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0);
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ }
+ }
+
+ /**
+ * Ensures rebind on package change.
+ */
+ @Test
+ public void testAttemptOnPackageChange() {
+ mConnectionManager.start();
+ verify(mConnection).bind();
+ ArgumentCaptor<Observer.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(Observer.Callback.class);
+ captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class));
+
+ verify(mObserver).addCallback(callbackCaptor.capture());
+
+ callbackCaptor.getValue().onSourceChanged();
+ verify(mConnection).bind();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index c9462d6..b380553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -33,7 +33,6 @@
import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Process;
-import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
@@ -43,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -57,8 +57,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -81,7 +79,7 @@
@Mock
private NotificationManager mNotificationManager;
@Mock
- private Vibrator mVibrator;
+ private VibratorHelper mVibrator;
@Mock
private IAudioService mIAudioService;
@Mock
@@ -110,7 +108,7 @@
mThreadFactory.setLooper(TestableLooper.get(this).getLooper());
mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
- mNotificationManager, Optional.of(mVibrator), mIAudioService, mAccessibilityManager,
+ mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
mPackageManager, mWakefullnessLifcycle, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -181,7 +179,7 @@
ThreadFactory theadFactory,
AudioManager audioManager,
NotificationManager notificationManager,
- Optional<Vibrator> optionalVibrator,
+ VibratorHelper optionalVibrator,
IAudioService iAudioService,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9c49e98..ca37a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1339,6 +1339,22 @@
assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
}
+ @Test
+ public void testStackViewOnBackPressed_updatesBubbleDataExpandState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ // Expand the stack
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+
+ // Hit back
+ BubbleStackView stackView = mBubbleController.getStackView();
+ stackView.onBackPressed();
+
+ // Make sure we're collapsed
+ assertStackCollapsed();
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index e12a82a..d82671d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -1158,6 +1158,22 @@
assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
}
+ @Test
+ public void testStackViewOnBackPressed_updatesBubbleDataExpandState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ // Expand the stack
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+
+ // Hit back
+ BubbleStackView stackView = mBubbleController.getStackView();
+ stackView.onBackPressed();
+
+ // Make sure we're collapsed
+ assertStackCollapsed();
+ }
+
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 196c6aa..177b080 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -282,6 +282,10 @@
// Notify the user to set up dream
NOTE_SETUP_DREAM = 68;
+ // Inform the user that MTE override is active.
+ // Package: android
+ NOTE_MTE_OVERRIDE_ENABLED = 69;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
@@ -362,5 +366,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..b0a5c66 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",
@@ -101,6 +102,7 @@
":services.usage-sources",
":services.usb-sources",
":services.voiceinteraction-sources",
+ ":services.wallpapereffectsgeneration-sources",
":services.wifi-sources",
],
visibility: ["//visibility:private"],
@@ -133,6 +135,7 @@
"services.appwidget",
"services.autofill",
"services.backup",
+ "services.cloudsearch",
"services.companion",
"services.contentcapture",
"services.contentsuggestions",
@@ -156,6 +159,7 @@
"services.usage",
"services.usb",
"services.voiceinteraction",
+ "services.wallpapereffectsgeneration",
"services.wifi",
"service-blobstore",
"service-jobscheduler",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e93ac47..8b62a64 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -66,6 +66,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -269,6 +270,7 @@
void onDoubleTap(int displayId);
void onDoubleTapAndHold(int displayId);
+
}
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
@@ -2164,4 +2166,23 @@
public void onDoubleTapAndHold(int displayId) {
mSystemSupport.onDoubleTapAndHold(displayId);
}
-}
+
+ /**
+ * Sets the scaling factor for animations.
+ */
+ public void setAnimationScale(float scale) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putFloat(
+ mContext.getContentResolver(), Settings.Global.WINDOW_ANIMATION_SCALE, scale);
+ Settings.Global.putFloat(
+ mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ scale);
+ Settings.Global.putFloat(
+ mContext.getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, scale);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 6744ea8..803177b 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -295,6 +295,21 @@
case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK :
sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_UP:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_DOWN:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_DOWN);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_LEFT:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT);
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_DPAD_CENTER:
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
+ return true;
default:
Slog.e(TAG, "Invalid action id: " + actionId);
return false;
diff --git a/services/api/current.txt b/services/api/current.txt
index 50f0052..dcf7e64 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -39,6 +39,7 @@
public interface ActivityManagerLocal {
method public boolean canStartForegroundService(int, int, @NonNull String);
+ method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException;
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 78d9095..a65d5b3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -89,6 +89,7 @@
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.IntArray;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Slog;
@@ -1935,6 +1936,14 @@
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
+ if (widget.trackingUpdate) {
+ // This is the first update, end the trace
+ widget.trackingUpdate = false;
+ Log.i(TAG, "Widget update received " + widget.toString());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "appwidget update-intent " + widget.provider.id.toString(),
+ widget.appWidgetId);
+ }
widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
}
if (widget == null || widget.provider == null || widget.provider.zombie
@@ -2011,6 +2020,15 @@
private void scheduleNotifyAppWidgetRemovedLocked(Widget widget) {
long requestId = UPDATE_COUNTER.incrementAndGet();
if (widget != null) {
+ if (widget.trackingUpdate) {
+ // Widget is being removed without any update, end the trace
+ widget.trackingUpdate = false;
+ Log.i(TAG, "Widget removed " + widget.toString());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "appwidget update-intent " + widget.provider.id.toString(),
+ widget.appWidgetId);
+ }
+
widget.updateSequenceNos.clear();
}
if (widget == null || widget.provider == null || widget.provider.zombie
@@ -2724,6 +2742,13 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appwidget init " + provider.id.componentName.getPackageName());
sendEnableIntentLocked(provider);
+ provider.widgets.forEach(widget -> {
+ widget.trackingUpdate = true;
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "appwidget update-intent " + provider.id.toString(),
+ widget.appWidgetId);
+ Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
+ });
int[] appWidgetIds = getWidgetIds(provider.widgets);
sendUpdateIntentLocked(provider, appWidgetIds);
registerForBroadcastsLocked(provider, appWidgetIds);
@@ -3321,8 +3346,10 @@
// Isolate the changes relating to RROs. The app info must be copied to prevent
// affecting other parts of system server that may have cached this app info.
oldAppInfo = new ApplicationInfo(oldAppInfo);
- oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone();
- oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone();
+ oldAppInfo.overlayPaths = newAppInfo.overlayPaths == null
+ ? null : newAppInfo.overlayPaths.clone();
+ oldAppInfo.resourceDirs = newAppInfo.resourceDirs == null
+ ? null : newAppInfo.resourceDirs.clone();
provider.info.providerInfo.applicationInfo = oldAppInfo;
for (int j = 0, M = provider.widgets.size(); j < M; j++) {
@@ -4249,6 +4276,7 @@
Host host;
// Map of request type to updateSequenceNo.
SparseLongArray updateSequenceNos = new SparseLongArray(2);
+ boolean trackingUpdate = false;
@Override
public String toString() {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index aa42e8d..1cff374 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -284,7 +284,9 @@
}
final Session session = mSessions.get(sessionId);
if (session != null && uid == session.uid) {
- session.setAuthenticationResultLocked(data, authenticationId);
+ synchronized (session.mLock) {
+ session.setAuthenticationResultLocked(data, authenticationId);
+ }
}
}
@@ -374,7 +376,9 @@
+ " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly;
mMaster.logRequestLocked(historyItem);
- newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+ synchronized (newSession.mLock) {
+ newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
+ }
if (forAugmentedAutofillOnly) {
// Must embed the flag in the response, at the high-end side of the long.
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a4bf52a..76ee728 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -17,6 +17,8 @@
package com.android.server.autofill;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
+import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
@@ -46,13 +48,16 @@
import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.IAssistDataReceiver;
+import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
+import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
@@ -147,12 +152,16 @@
AutoFillUI.AutoFillUiCallback, ValueFinder {
private static final String TAG = "AutofillSession";
+ private static final String ACTION_DELAYED_FILL =
+ "android.service.autofill.action.DELAYED_FILL";
private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
+ final Object mLock;
+
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
- private final Object mLock;
private final AutoFillUI mUi;
+ @NonNull private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -267,6 +276,12 @@
*/
private boolean mHasCallback;
+ @GuardedBy("mLock")
+ private boolean mDelayedFillBroadcastReceiverRegistered;
+
+ @GuardedBy("mLock")
+ private PendingIntent mDelayedFillPendingIntent;
+
/**
* Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
* saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
@@ -354,6 +369,32 @@
private final AccessibilityManager mAccessibilityManager;
+ // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
+ // new one per Session.
+ private final BroadcastReceiver mDelayedFillBroadcastReceiver =
+ new BroadcastReceiver() {
+ // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by
+ // 'Session.this.mLock', which is the same as mLock.
+ @SuppressWarnings("GuardedBy")
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (!intent.getAction().equals(ACTION_DELAYED_FILL)) {
+ Slog.wtf(TAG, "Unexpected action is received.");
+ return;
+ }
+ if (!intent.hasExtra(EXTRA_REQUEST_ID)) {
+ Slog.e(TAG, "Delay fill action is missing request id extra.");
+ return;
+ }
+ Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received");
+ synchronized (mLock) {
+ int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
+ FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+ mAssistReceiver.processDelayedFillLocked(requestId, response);
+ }
+ }
+ };
+
void onSwitchInputMethodLocked() {
// One caveat is that for the case where the focus is on a field for which regular autofill
// returns null, and augmented autofill is triggered, and then the user switches the input
@@ -406,28 +447,25 @@
*/
private final class SessionFlags {
/** Whether autofill is disabled by the service */
- @GuardedBy("mLock")
private boolean mAutofillDisabled;
/** Whether the autofill service supports inline suggestions */
- @GuardedBy("mLock")
private boolean mInlineSupportedByService;
/** True if session is for augmented only */
- @GuardedBy("mLock")
private boolean mAugmentedAutofillOnly;
/** Whether the session is currently showing the SaveUi. */
- @GuardedBy("mLock")
private boolean mShowingSaveUi;
/** Whether the current {@link FillResponse} is expired. */
- @GuardedBy("mLock")
private boolean mExpiredResponse;
/** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
- @GuardedBy("mLock")
private boolean mClientSuggestionsEnabled;
+
+ /** Whether the fill dialog UI is disabled. */
+ private boolean mFillDialogDisabled;
}
/**
@@ -441,6 +479,8 @@
private InlineSuggestionsRequest mPendingInlineSuggestionsRequest;
@GuardedBy("mLock")
private FillRequest mPendingFillRequest;
+ @GuardedBy("mLock")
+ private FillRequest mLastFillRequest;
@Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState,
boolean isInlineRequest) {
@@ -467,6 +507,7 @@
mPendingInlineSuggestionsRequest = inlineRequest;
}
+ @GuardedBy("mLock")
void maybeRequestFillFromServiceLocked() {
if (mPendingFillRequest == null) {
return;
@@ -484,9 +525,12 @@
mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
mPendingFillRequest.getFillContexts(),
mPendingFillRequest.getClientState(),
- mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest);
+ mPendingFillRequest.getFlags(),
+ mPendingInlineSuggestionsRequest,
+ mPendingFillRequest.getDelayedFillIntentSender());
}
}
+ mLastFillRequest = mPendingFillRequest;
mRemoteFillService.onFillRequest(mPendingFillRequest);
mPendingInlineSuggestionsRequest = null;
@@ -588,8 +632,12 @@
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
+ mDelayedFillPendingIntent = createPendingIntent(requestId);
request = new FillRequest(requestId, contexts, mClientState, flags,
- /*inlineSuggestionsRequest=*/null);
+ /*inlineSuggestionsRequest=*/ null,
+ /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null
+ ? null
+ : mDelayedFillPendingIntent.getIntentSender());
mPendingFillRequest = request;
maybeRequestFillFromServiceLocked();
@@ -604,7 +652,70 @@
public void onHandleAssistScreenshot(Bitmap screenshot) {
// Do nothing
}
- };
+
+ @GuardedBy("mLock")
+ void processDelayedFillLocked(int requestId, FillResponse response) {
+ if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) {
+ Slog.v(TAG, "processDelayedFillLocked: "
+ + "calling onFillRequestSuccess with new response");
+ onFillRequestSuccess(requestId, response,
+ mService.getServicePackageName(), mLastFillRequest.getFlags());
+ }
+ }
+ }
+
+ /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */
+ private PendingIntent createPendingIntent(int requestId) {
+ Slog.d(TAG, "createPendingIntent for request " + requestId);
+ PendingIntent pendingIntent;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android")
+ .putExtra(EXTRA_REQUEST_ID, requestId);
+ pendingIntent = PendingIntent.getBroadcast(
+ mContext, this.id, intent,
+ PendingIntent.FLAG_MUTABLE
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return pendingIntent;
+ }
+
+ @GuardedBy("mLock")
+ private void clearPendingIntentLocked() {
+ Slog.d(TAG, "clearPendingIntentLocked");
+ if (mDelayedFillPendingIntent == null) {
+ return;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mDelayedFillPendingIntent.cancel();
+ mDelayedFillPendingIntent = null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void registerDelayedFillBroadcastLocked() {
+ if (!mDelayedFillBroadcastReceiverRegistered) {
+ Slog.v(TAG, "registerDelayedFillBroadcastLocked()");
+ IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL);
+ mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter);
+ mDelayedFillBroadcastReceiverRegistered = true;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unregisterDelayedFillBroadcastLocked() {
+ if (mDelayedFillBroadcastReceiverRegistered) {
+ Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()");
+ mContext.unregisterReceiver(mDelayedFillBroadcastReceiver);
+ mDelayedFillBroadcastReceiverRegistered = false;
+ }
+ }
/**
* Returns the ids of all entries in {@link #mViewStates} in the same order.
@@ -860,7 +971,7 @@
mService.getRemoteInlineSuggestionRenderServiceLocked();
if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
&& remoteRenderService != null
- && isViewFocusedLocked(flags)) {
+ && (isViewFocusedLocked(flags) || (isRequestFromActivityStarted(flags)))) {
final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
if (mSessionFlags.mClientSuggestionsEnabled) {
final int finalRequestId = requestId;
@@ -906,6 +1017,10 @@
requestAssistStructureLocked(requestId, flags);
}
+ private boolean isRequestFromActivityStarted(int flags) {
+ return (flags & FLAG_ACTIVITY_START) != 0;
+ }
+
@GuardedBy("mLock")
private void requestAssistStructureLocked(int requestId, int flags) {
try {
@@ -954,6 +1069,7 @@
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
mWtfHistory = wtfHistory;
+ mContext = context;
mComponentName = componentName;
mCompatMode = compatMode;
mSessionState = STATE_ACTIVE;
@@ -1086,6 +1202,12 @@
processNullResponseLocked(requestId, requestFlags);
return;
}
+
+ final int flags = response.getFlags();
+ if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
+ Slog.v(TAG, "Service requested to wait for delayed fill response.");
+ registerDelayedFillBroadcastLocked();
+ }
}
mService.setLastResponse(id, response);
@@ -1196,6 +1318,7 @@
@Nullable CharSequence message) {
boolean showMessage = !TextUtils.isEmpty(message);
synchronized (mLock) {
+ unregisterDelayedFillBroadcastLocked();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId
+ ") rejected - session: " + id + " destroyed");
@@ -1501,6 +1624,23 @@
this, intentSender, intent));
}
+ // AutoFillUiCallback
+ @Override
+ public void requestShowSoftInput(AutofillId id) {
+ IAutoFillManagerClient client = getClient();
+ if (client != null) {
+ try {
+ client.requestShowSoftInput(id);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending input show up notification", e);
+ }
+ }
+ synchronized (mLock) {
+ // stop to show fill dialog
+ mSessionFlags.mFillDialogDisabled = true;
+ }
+ }
+
private void notifyFillUiHidden(@NonNull AutofillId autofillId) {
synchronized (mLock) {
try {
@@ -2869,6 +3009,9 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
+ if (!isRequestFromActivityStarted(flags)) {
+ mSessionFlags.mFillDialogDisabled = true;
+ }
requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
@@ -2958,6 +3101,7 @@
}
if (isSameViewEntered) {
+ setFillDialogDisabledAndStartInput();
return;
}
@@ -2968,6 +3112,7 @@
if (Objects.equals(mCurrentViewId, viewState.id)) {
if (sVerbose) Slog.v(TAG, "Exiting view " + id);
mUi.hideFillUi(this);
+ mUi.hideFillDialog(this);
hideAugmentedAutofillLocked(viewState);
// We don't send an empty response to IME so that it doesn't cause UI flicker
// on the IME side if it arrives before the input view is finished on the IME.
@@ -3148,6 +3293,17 @@
return;
}
+ if (requestShowFillDialog(response, filledId, filterText)) {
+ synchronized (mLock) {
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+ mService.logDatasetShown(id, mClientState);
+ }
+ return;
+ }
+
+ setFillDialogDisabled();
+
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -3192,6 +3348,81 @@
}
}
+ @GuardedBy("mLock")
+ private void updateFillDialogTriggerIdsLocked() {
+ final FillResponse response = getLastResponseLocked(null);
+
+ if (response == null) return;
+
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ notifyClientFillDialogTriggerIds(ids == null ? null : Arrays.asList(ids));
+ }
+
+ private void notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds) {
+ try {
+ if (sVerbose) {
+ Slog.v(TAG, "notifyFillDialogTriggerIds(): " + fieldIds);
+ }
+ getClient().notifyFillDialogTriggerIds(fieldIds);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Cannot set trigger ids for fill dialog", e);
+ }
+ }
+
+ private boolean isFillDialogUiEnabled() {
+ // TODO read from Settings or somewhere
+ final boolean isSettingsEnabledFillDialog = true;
+ synchronized (mLock) {
+ return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+ }
+ }
+
+ private void setFillDialogDisabled() {
+ synchronized (mLock) {
+ mSessionFlags.mFillDialogDisabled = true;
+ }
+ notifyClientFillDialogTriggerIds(null);
+ }
+
+ private void setFillDialogDisabledAndStartInput() {
+ if (getUiForShowing().isFillDialogShowing()) {
+ setFillDialogDisabled();
+ final AutofillId id;
+ synchronized (mLock) {
+ id = mCurrentViewId;
+ }
+ requestShowSoftInput(id);
+ }
+ }
+
+ private boolean requestShowFillDialog(FillResponse response,
+ AutofillId filledId, String filterText) {
+ if (!isFillDialogUiEnabled()) {
+ // Unsupported fill dialog UI
+ return false;
+ }
+
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ if (ids == null || !ArrayUtils.contains(ids, filledId)) {
+ return false;
+ }
+
+ final Drawable serviceIcon = getServiceIcon();
+
+ getUiForShowing().showFillDialog(filledId, response, filterText,
+ mService.getServicePackageName(), mComponentName, serviceIcon, this);
+ return true;
+ }
+
+ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's
+ // actually the same object as mLock.
+ // TODO: Expose mService.mLock or redesign instead.
+ private Drawable getServiceIcon() {
+ synchronized (mLock) {
+ return mService.getServiceIconLocked();
+ }
+ }
+
/**
* Returns whether we made a request to show inline suggestions.
*/
@@ -3412,6 +3643,7 @@
@GuardedBy("mLock")
private void processNullResponseLocked(int requestId, int flags) {
+ unregisterDelayedFillBroadcastLocked();
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
}
@@ -3584,9 +3816,10 @@
mService.getRemoteInlineSuggestionRenderServiceLocked();
if (remoteRenderService != null
&& (mSessionFlags.mAugmentedAutofillOnly
- || !mSessionFlags.mInlineSupportedByService
- || mSessionFlags.mExpiredResponse)
- && isViewFocusedLocked(flags)) {
+ || !mSessionFlags.mInlineSupportedByService
+ || mSessionFlags.mExpiredResponse)
+ && isViewFocusedLocked(flags)
+ || isFillDialogUiEnabled()) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
(extras) -> {
@@ -3625,6 +3858,11 @@
// only if handling the current response requires it.
mUi.hideAll(this);
+ if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) {
+ Slog.d(TAG, "Service did not request to wait for delayed fill response.");
+ unregisterDelayedFillBroadcastLocked();
+ }
+
final int requestId = newResponse.getRequestId();
if (sVerbose) {
Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
@@ -3642,6 +3880,7 @@
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
+ updateFillDialogTriggerIdsLocked();
updateTrackedIdsLocked();
if (mCurrentViewId == null) {
@@ -4176,6 +4415,9 @@
return null;
}
+ clearPendingIntentLocked();
+ unregisterDelayedFillBroadcastLocked();
+
unlinkClientVultureLocked();
mUi.destroyAll(mPendingSaveUi, this, true);
mUi.clearCallback(this);
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index adb1e3e..4a14f14 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -82,6 +82,8 @@
public static final int STATE_INLINE_DISABLED = 0x8000;
/** The View is waiting for an inline suggestions request from IME.*/
public static final int STATE_PENDING_CREATE_INLINE_REQUEST = 0x10000;
+ /** Fill dialog were shown for this View. */
+ public static final int STATE_FILL_DIALOG_SHOWN = 0x20000;
public final AutofillId id;
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 71c3c16..056ab92 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -66,6 +66,7 @@
private @Nullable FillUi mFillUi;
private @Nullable SaveUi mSaveUi;
+ private @Nullable DialogFillUi mFillDialog;
private @Nullable AutoFillUiCallback mCallback;
@@ -90,6 +91,7 @@
void startIntentSender(IntentSender intentSender, Intent intent);
void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
void cancelSession();
+ void requestShowSoftInput(AutofillId id);
}
public AutoFillUI(@NonNull Context context) {
@@ -155,6 +157,12 @@
}
/**
+ * Hides the fill UI.
+ */
+ public void hideFillDialog(@NonNull AutoFillUiCallback callback) {
+ mHandler.post(() -> hideFillDialogUiThread(callback));
+ }
+ /**
* Filters the options in the fill UI.
*
* @param filterText The filter prefix.
@@ -369,6 +377,62 @@
}
/**
+ * Shows the UI asking the user to choose for autofill.
+ */
+ public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
+ @Nullable String filterText, @Nullable String servicePackageName,
+ @NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
+ @NonNull AutoFillUiCallback callback) {
+ if (sVerbose) {
+ Slog.v(TAG, "showFillDialog for "
+ + componentName.toShortString() + ": " + response);
+ }
+
+ // TODO: enable LogMaker
+
+ mHandler.post(() -> {
+ if (callback != mCallback) {
+ return;
+ }
+ hideAllUiThread(callback);
+ mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText,
+ serviceIcon, servicePackageName, componentName, mOverlayControl,
+ mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
+ @Override
+ public void onResponsePicked(FillResponse response) {
+ hideFillDialogUiThread(callback);
+ if (mCallback != null) {
+ mCallback.authenticate(response.getRequestId(),
+ AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED,
+ response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ false);
+ }
+ }
+
+ @Override
+ public void onDatasetPicked(Dataset dataset) {
+ hideFillDialogUiThread(callback);
+ if (mCallback != null) {
+ final int datasetIndex = response.getDatasets().indexOf(dataset);
+ mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+ }
+
+ @Override
+ public void onCanceled() {
+ hideFillDialogUiThread(callback);
+ callback.requestShowSoftInput(focusedId);
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender) {
+ mCallback.startIntentSenderAndFinishSession(intentSender);
+ }
+ });
+ });
+ }
+
+ /**
* Executes an operation in the pending save UI, if any.
*/
public void onPendingSaveUi(int operation, @NonNull IBinder token) {
@@ -400,6 +464,10 @@
return mSaveUi == null ? false : mSaveUi.isShowing();
}
+ public boolean isFillDialogShowing() {
+ return mFillDialog == null ? false : mFillDialog.isShowing();
+ }
+
public void dump(PrintWriter pw) {
pw.println("Autofill UI");
final String prefix = " ";
@@ -417,6 +485,12 @@
} else {
pw.print(prefix); pw.println("showsSaveUi: false");
}
+ if (mFillDialog != null) {
+ pw.print(prefix); pw.println("showsFillDialog: true");
+ mFillDialog.dump(pw, prefix2);
+ } else {
+ pw.print(prefix); pw.println("showsFillDialog: false");
+ }
}
@android.annotation.UiThread
@@ -442,6 +516,14 @@
}
@android.annotation.UiThread
+ private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) {
+ if (mFillDialog != null && (callback == null || callback == mCallback)) {
+ mFillDialog.destroy();
+ mFillDialog = null;
+ }
+ }
+
+ @android.annotation.UiThread
private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
if (mSaveUi == null) {
// Calling destroySaveUiUiThread() twice is normal - it usually happens when the
@@ -475,12 +557,14 @@
private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
@Nullable AutoFillUiCallback callback, boolean notifyClient) {
hideFillUiUiThread(callback, notifyClient);
+ hideFillDialogUiThread(callback);
destroySaveUiUiThread(pendingSaveUi, notifyClient);
}
@android.annotation.UiThread
private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
hideFillUiUiThread(callback, true);
+ hideFillDialogUiThread(callback);
final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
if (sDebug) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
new file mode 100644
index 0000000..e122993
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.autofill.ui;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentSender;
+import android.graphics.drawable.Drawable;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.text.TextUtils;
+import android.util.PluralsMessageFormatter;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.server.autofill.AutofillManagerService;
+
+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.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * A dialog to show Autofill suggestions.
+ *
+ * This fill dialog UI shows as a bottom sheet style dialog. This dialog UI
+ * provides a larger area to display the suggestions, it provides a more
+ * conspicuous and efficient interface to the user. So it is easy for users
+ * to pay attention to the datasets and selecting one of them.
+ */
+final class DialogFillUi {
+
+ private static final String TAG = "DialogFillUi";
+ private static final int THEME_ID_LIGHT =
+ R.style.Theme_DeviceDefault_Light_Autofill_Save;
+ private static final int THEME_ID_DARK =
+ R.style.Theme_DeviceDefault_Autofill_Save;
+
+ interface UiCallback {
+ void onResponsePicked(@NonNull FillResponse response);
+ void onDatasetPicked(@NonNull Dataset dataset);
+ void onCanceled();
+ void startIntentSender(IntentSender intentSender);
+ }
+
+ private final @NonNull Dialog mDialog;
+ private final @NonNull OverlayControl mOverlayControl;
+ private final String mServicePackageName;
+ private final ComponentName mComponentName;
+ private final int mThemeId;
+ private final @NonNull Context mContext;
+ private final @NonNull UiCallback mCallback;
+ private final @NonNull ListView mListView;
+ private final @Nullable ItemsAdapter mAdapter;
+ private final int mVisibleDatasetsMaxCount;
+
+ private @Nullable String mFilterText;
+ private @Nullable AnnounceFilterResult mAnnounceFilterResult;
+ private boolean mDestroyed;
+
+ DialogFillUi(@NonNull Context context, @NonNull FillResponse response,
+ @NonNull AutofillId focusedViewId, @Nullable String filterText,
+ @Nullable Drawable serviceIcon, @Nullable String servicePackageName,
+ @Nullable ComponentName componentName, @NonNull OverlayControl overlayControl,
+ boolean nightMode, @NonNull UiCallback callback) {
+ if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode);
+ mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT;
+ mCallback = callback;
+ mOverlayControl = overlayControl;
+ mServicePackageName = servicePackageName;
+ mComponentName = componentName;
+
+ mContext = new ContextThemeWrapper(context, mThemeId);
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null);
+
+ setServiceIcon(decor, serviceIcon);
+ setHeader(decor, response);
+
+ mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount();
+
+ if (response.getAuthentication() != null) {
+ mListView = null;
+ mAdapter = null;
+ try {
+ initialAuthenticationLayout(decor, response);
+ } catch (RuntimeException e) {
+ callback.onCanceled();
+ Slog.e(TAG, "Error inflating remote views", e);
+ mDialog = null;
+ return;
+ }
+ } else {
+ final List<ViewItem> items = createDatasetItems(response, focusedViewId);
+ mAdapter = new ItemsAdapter(items);
+ mListView = decor.findViewById(R.id.autofill_dialog_list);
+ initialDatasetLayout(decor, filterText);
+ }
+
+ setDismissButton(decor);
+
+ mDialog = new Dialog(mContext, mThemeId);
+ mDialog.setContentView(decor);
+ setDialogParamsAsBottomSheet();
+
+ show();
+ }
+
+ private int getVisibleDatasetsMaxCount() {
+ if (AutofillManagerService.getVisibleDatasetsMaxCount() > 0) {
+ final int maxCount = AutofillManagerService.getVisibleDatasetsMaxCount();
+ if (sVerbose) {
+ Slog.v(TAG, "overriding maximum visible datasets to " + maxCount);
+ }
+ return maxCount;
+ } else {
+ return mContext.getResources()
+ .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
+ }
+ }
+
+ private void setDialogParamsAsBottomSheet() {
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
+ window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+ window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
+ window.setCloseOnTouchOutside(true);
+ final WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.accessibilityTitle =
+ mContext.getString(R.string.autofill_picker_accessibility_title);
+ params.windowAnimations = R.style.AutofillSaveAnimation;
+ }
+
+ private void setServiceIcon(View decor, Drawable serviceIcon) {
+ if (serviceIcon == null) {
+ return;
+ }
+
+ final ImageView iconView = decor.findViewById(R.id.autofill_service_icon);
+ final int actualWidth = serviceIcon.getMinimumWidth();
+ final int actualHeight = serviceIcon.getMinimumHeight();
+ if (sDebug) {
+ Slog.d(TAG, "Adding service icon "
+ + "(" + actualWidth + "x" + actualHeight + ")");
+ }
+ iconView.setImageDrawable(serviceIcon);
+ iconView.setVisibility(View.VISIBLE);
+ }
+
+ private void setHeader(View decor, FillResponse response) {
+ final RemoteViews presentation = response.getDialogHeader();
+ if (presentation == null) {
+ return;
+ }
+
+ final ViewGroup container = decor.findViewById(R.id.autofill_dialog_header);
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+
+ final View content = presentation.applyWithTheme(
+ mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+ container.addView(content);
+ container.setVisibility(View.VISIBLE);
+ }
+
+ private void setDismissButton(View decor) {
+ final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
+ noButton.setOnClickListener((v) -> mCallback.onCanceled());
+ }
+
+ private void setContinueButton(View decor, View.OnClickListener listener) {
+ final TextView yesButton = decor.findViewById(R.id.autofill_dialog_yes);
+ // set "Continue" by default
+ yesButton.setText(R.string.autofill_continue_yes);
+ yesButton.setOnClickListener(listener);
+ }
+
+ private void initialAuthenticationLayout(View decor, FillResponse response) {
+ RemoteViews presentation = response.getDialogPresentation();
+ if (presentation == null) {
+ presentation = response.getPresentation();
+ }
+ if (presentation == null) {
+ throw new RuntimeException("No presentation for fill dialog authentication");
+ }
+
+ // insert authentication item under autofill_dialog_container
+ final ViewGroup container = decor.findViewById(R.id.autofill_dialog_container);
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+ final View content = presentation.applyWithTheme(
+ mContext, (ViewGroup) decor, interceptionHandler, mThemeId);
+ container.addView(content);
+ container.setVisibility(View.VISIBLE);
+ container.setFocusable(true);
+ container.setOnClickListener(v -> mCallback.onResponsePicked(response));
+ // just single item, set up continue button
+ setContinueButton(decor, v -> mCallback.onResponsePicked(response));
+ }
+
+ private ArrayList<ViewItem> createDatasetItems(FillResponse response,
+ AutofillId focusedViewId) {
+ final int datasetCount = response.getDatasets().size();
+ if (sVerbose) {
+ Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: "
+ + mVisibleDatasetsMaxCount);
+ }
+
+ final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> {
+ if (pendingIntent != null) {
+ mCallback.startIntentSender(pendingIntent.getIntentSender());
+ }
+ return true;
+ };
+
+ final ArrayList<ViewItem> items = new ArrayList<>(datasetCount);
+ for (int i = 0; i < datasetCount; i++) {
+ final Dataset dataset = response.getDatasets().get(i);
+ final int index = dataset.getFieldIds().indexOf(focusedViewId);
+ if (index >= 0) {
+ RemoteViews presentation = dataset.getFieldDialogPresentation(index);
+ if (presentation == null) {
+ Slog.w(TAG, "fallback to presentation");
+ presentation = dataset.getFieldPresentation(index);
+ }
+ if (presentation == null) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+ continue;
+ }
+ final View view;
+ try {
+ if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
+ view = presentation.applyWithTheme(
+ mContext, null, interceptionHandler, mThemeId);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Error inflating remote views", e);
+ continue;
+ }
+ // TODO: Extract the shared filtering logic here and in FillUi to a common
+ // method.
+ final Dataset.DatasetFieldFilter filter = dataset.getFilter(index);
+ Pattern filterPattern = null;
+ String valueText = null;
+ boolean filterable = true;
+ if (filter == null) {
+ final AutofillValue value = dataset.getFieldValues().get(index);
+ if (value != null && value.isText()) {
+ valueText = value.getTextValue().toString().toLowerCase();
+ }
+ } else {
+ filterPattern = filter.pattern;
+ if (filterPattern == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "Explicitly disabling filter at id " + focusedViewId
+ + " for dataset #" + index);
+ }
+ filterable = false;
+ }
+ }
+
+ items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
+ }
+ }
+ return items;
+ }
+
+ private void initialDatasetLayout(View decor, String filterText) {
+ final AdapterView.OnItemClickListener onItemClickListener =
+ (adapter, view, position, id) -> {
+ final ViewItem vi = mAdapter.getItem(position);
+ mCallback.onDatasetPicked(vi.dataset);
+ };
+
+ mListView.setAdapter(mAdapter);
+ mListView.setVisibility(View.VISIBLE);
+ mListView.setOnItemClickListener(onItemClickListener);
+
+ if (mAdapter.getCount() == 1) {
+ // just single item, set up continue button
+ setContinueButton(decor, (v) ->
+ onItemClickListener.onItemClick(null, null, 0, 0));
+ }
+
+ if (filterText == null) {
+ mFilterText = null;
+ } else {
+ mFilterText = filterText.toLowerCase();
+ }
+
+ final int oldCount = mAdapter.getCount();
+ mAdapter.getFilter().filter(mFilterText, (count) -> {
+ if (mDestroyed) {
+ return;
+ }
+ if (count <= 0) {
+ if (sDebug) {
+ final int size = mFilterText == null ? 0 : mFilterText.length();
+ Slog.d(TAG, "No dataset matches filter with " + size + " chars");
+ }
+ mCallback.onCanceled();
+ } else {
+
+ if (mAdapter.getCount() > mVisibleDatasetsMaxCount) {
+ mListView.setVerticalScrollBarEnabled(true);
+ mListView.onVisibilityAggregated(true);
+ } else {
+ mListView.setVerticalScrollBarEnabled(false);
+ }
+ if (mAdapter.getCount() != oldCount) {
+ mListView.requestLayout();
+ }
+ }
+ });
+ }
+
+ private void show() {
+ Slog.i(TAG, "Showing fill dialog");
+ mDialog.show();
+ mOverlayControl.hideOverlays();
+ }
+
+ boolean isShowing() {
+ return mDialog.isShowing();
+ }
+
+ void hide() {
+ if (sVerbose) Slog.v(TAG, "Hiding fill dialog.");
+ try {
+ mDialog.hide();
+ } finally {
+ mOverlayControl.showOverlays();
+ }
+ }
+
+ void destroy() {
+ try {
+ if (sDebug) Slog.d(TAG, "destroy()");
+ throwIfDestroyed();
+
+ mDialog.dismiss();
+ mDestroyed = true;
+ } finally {
+ mOverlayControl.showOverlays();
+ }
+ }
+
+ private void throwIfDestroyed() {
+ if (mDestroyed) {
+ throw new IllegalStateException("cannot interact with a destroyed instance");
+ }
+ }
+
+ @Override
+ public String toString() {
+ // TODO toString
+ return "NO TITLE";
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+
+ pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+ pw.print(prefix); pw.print("app: "); pw.println(mComponentName.toShortString());
+ pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId);
+ switch (mThemeId) {
+ case THEME_ID_DARK:
+ pw.println(" (dark)");
+ break;
+ case THEME_ID_LIGHT:
+ pw.println(" (light)");
+ break;
+ default:
+ pw.println("(UNKNOWN_MODE)");
+ break;
+ }
+ final View view = mDialog.getWindow().getDecorView();
+ final int[] loc = view.getLocationOnScreen();
+ pw.print(prefix); pw.print("coordinates: ");
+ pw.print('('); pw.print(loc[0]); pw.print(','); pw.print(loc[1]); pw.print(')');
+ pw.print('(');
+ pw.print(loc[0] + view.getWidth()); pw.print(',');
+ pw.print(loc[1] + view.getHeight()); pw.println(')');
+ pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed);
+ }
+
+ private void announceSearchResultIfNeeded() {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (mAnnounceFilterResult == null) {
+ mAnnounceFilterResult = new AnnounceFilterResult();
+ }
+ mAnnounceFilterResult.post();
+ }
+ }
+
+ // TODO: Below code copied from FullUi, Extract the shared filtering logic here
+ // and in FillUi to a common method.
+ private final class AnnounceFilterResult implements Runnable {
+ private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
+
+ public void post() {
+ remove();
+ mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
+ }
+
+ public void remove() {
+ mListView.removeCallbacks(this);
+ }
+
+ @Override
+ public void run() {
+ final int count = mListView.getAdapter().getCount();
+ final String text;
+ if (count <= 0) {
+ text = mContext.getString(R.string.autofill_picker_no_suggestions);
+ } else {
+ 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);
+ }
+ }
+
+ private final class ItemsAdapter extends BaseAdapter implements Filterable {
+ private @NonNull final List<ViewItem> mAllItems;
+
+ private @NonNull final List<ViewItem> mFilteredItems = new ArrayList<>();
+
+ ItemsAdapter(@NonNull List<ViewItem> items) {
+ mAllItems = Collections.unmodifiableList(new ArrayList<>(items));
+ mFilteredItems.addAll(items);
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence filterText) {
+ // No locking needed as mAllItems is final an immutable
+ final List<ViewItem> filtered = mAllItems.stream()
+ .filter((item) -> item.matches(filterText))
+ .collect(Collectors.toList());
+ final FilterResults results = new FilterResults();
+ results.values = filtered;
+ results.count = filtered.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ final boolean resultCountChanged;
+ final int oldItemCount = mFilteredItems.size();
+ mFilteredItems.clear();
+ if (results.count > 0) {
+ @SuppressWarnings("unchecked") final List<ViewItem> items =
+ (List<ViewItem>) results.values;
+ mFilteredItems.addAll(items);
+ }
+ resultCountChanged = (oldItemCount != mFilteredItems.size());
+ if (resultCountChanged) {
+ announceSearchResultIfNeeded();
+ }
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+ @Override
+ public int getCount() {
+ return mFilteredItems.size();
+ }
+
+ @Override
+ public ViewItem getItem(int position) {
+ return mFilteredItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return getItem(position).view;
+ }
+
+ @Override
+ public String toString() {
+ return "ItemsAdapter: [all=" + mAllItems + ", filtered=" + mFilteredItems + "]";
+ }
+ }
+
+
+ /**
+ * An item for the list view - either a (clickable) dataset or a (read-only) header / footer.
+ */
+ private static class ViewItem {
+ public final @Nullable String value;
+ public final @Nullable Dataset dataset;
+ public final @NonNull View view;
+ public final @Nullable Pattern filter;
+ public final boolean filterable;
+
+ /**
+ * Default constructor.
+ *
+ * @param dataset dataset associated with the item
+ * @param filter optional filter set by the service to determine how the item should be
+ * filtered
+ * @param filterable optional flag set by the service to indicate this item should not be
+ * filtered (typically used when the dataset has value but it's sensitive, like a password)
+ * @param value dataset value
+ * @param view dataset presentation.
+ */
+ ViewItem(@NonNull Dataset dataset, @Nullable Pattern filter, boolean filterable,
+ @Nullable String value, @NonNull View view) {
+ this.dataset = dataset;
+ this.value = value;
+ this.view = view;
+ this.filter = filter;
+ this.filterable = filterable;
+ }
+
+ /**
+ * Returns whether this item matches the value input by the user so it can be included
+ * in the filtered datasets.
+ */
+ public boolean matches(CharSequence filterText) {
+ if (TextUtils.isEmpty(filterText)) {
+ // Always show item when the user input is empty
+ return true;
+ }
+ if (!filterable) {
+ // Service explicitly disabled filtering using a null Pattern.
+ return false;
+ }
+ final String constraintLowerCase = filterText.toString().toLowerCase();
+ if (filter != null) {
+ // Uses pattern provided by service
+ return filter.matcher(constraintLowerCase).matches();
+ } else {
+ // Compares it with dataset value with dataset
+ return (value == null)
+ ? (dataset.getAuthentication() == null)
+ : value.toLowerCase().startsWith(constraintLowerCase);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("ViewItem:[view=")
+ .append(view.getAutofillId());
+ final String datasetId = dataset == null ? null : dataset.getId();
+ if (datasetId != null) {
+ builder.append(", dataset=").append(datasetId);
+ }
+ if (value != null) {
+ // Cannot print value because it could contain PII
+ builder.append(", value=").append(value.length()).append("_chars");
+ }
+ if (filterable) {
+ builder.append(", filterable");
+ }
+ if (filter != null) {
+ // Filter should not have PII, but it could be a huge regexp
+ builder.append(", filter=").append(filter.pattern().length()).append("_chars");
+ }
+ return builder.append(']').toString();
+ }
+ }
+}
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/TEST_MAPPING b/services/companion/TEST_MAPPING
index 63f54fa..4a37cb8 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -1,6 +1,12 @@
{
"presubmit": [
{
+ "name": "CtsCompanionDeviceManagerCoreTestCases"
+ },
+ {
+ "name": "CtsCompanionDeviceManagerUiAutomationTestCases"
+ },
+ {
"name": "CtsOsTestCases",
"options": [
{
diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
index f1d98f0..0509e0c 100644
--- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
+++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java
@@ -36,7 +36,6 @@
* will be killed if association/role are revoked.
*/
public class AssociationCleanUpService extends JobService {
- private static final String TAG = LOG_TAG + ".AssociationCleanUpService";
private static final int JOB_ID = AssociationCleanUpService.class.hashCode();
private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day
private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService(
@@ -56,7 +55,7 @@
@Override
public boolean onStopJob(final JobParameters params) {
- Slog.d(TAG, "Association cleanup job stopped; id=" + params.getJobId()
+ Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId()
+ ", reason="
+ JobParameters.getInternalReasonCodeDescription(
params.getInternalStopReasonCode()));
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index cda554e..21a677b 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
@@ -52,9 +53,10 @@
* Other system component (both inside and outside if the com.android.server.companion package)
* should use public {@link AssociationStore} interface.
*/
+@SuppressLint("LongLogTag")
class AssociationStoreImpl implements AssociationStore {
private static final boolean DEBUG = false;
- private static final String TAG = "AssociationStore";
+ private static final String TAG = "CompanionDevice_AssociationStore";
private final Object mLock = new Object();
@@ -125,7 +127,7 @@
// Update the MacAddress-to-List<Association> map if needed.
final MacAddress updatedAddress = updated.getDeviceMacAddress();
final MacAddress currentAddress = current.getDeviceMacAddress();
- macAddressChanged = Objects.equals(currentAddress, updatedAddress);
+ macAddressChanged = !Objects.equals(currentAddress, updatedAddress);
if (macAddressChanged) {
if (currentAddress != null) {
mAddressMap.get(currentAddress).remove(id);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index cfd3798..c3ab2a7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1255,7 +1255,7 @@
}
@Override
- public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {
+ public void onDeviceDisconnected(BluetoothDevice device, int reason) {
Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") "
+ BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason));
CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress());
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index f2e66077..fd13085 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,16 +16,17 @@
package com.android.server.companion;
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
-
import android.companion.AssociationInfo;
+import android.os.ShellCommand;
import android.util.Log;
import android.util.Slog;
import java.io.PrintWriter;
import java.util.List;
-class CompanionDeviceShellCommand extends android.os.ShellCommand {
+class CompanionDeviceShellCommand extends ShellCommand {
+ private static final String TAG = "CompanionDevice_ShellCommand";
+
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
@@ -84,7 +85,7 @@
}
return 0;
} catch (Throwable t) {
- Slog.e(LOG_TAG, "Error running a command: $ " + cmd, t);
+ Slog.e(TAG, "Error running a command: $ " + cmd, t);
getErrPrintWriter().println(Log.getStackTraceString(t));
return 1;
}
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
index 6055a81..8ac741a 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -25,7 +25,7 @@
import android.util.AtomicFile;
import android.util.Slog;
-import com.android.internal.util.FunctionalUtils;
+import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -34,8 +34,7 @@
import java.io.FileOutputStream;
final class DataStoreUtils {
-
- private static final String LOG_TAG = DataStoreUtils.class.getSimpleName();
+ private static final String TAG = "CompanionDevice_DataStoreUtils";
static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
throws XmlPullParserException {
@@ -71,12 +70,12 @@
* Writing to file could fail, for example, if the user has been recently removed and so was
* their DE (/data/system_de/<user-id>/) directory.
*/
- static void writeToFileSafely(@NonNull AtomicFile file,
- @NonNull FunctionalUtils.ThrowingConsumer<FileOutputStream> consumer) {
+ static void writeToFileSafely(
+ @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) {
try {
file.write(consumer);
} catch (Exception e) {
- Slog.e(LOG_TAG, "Error while writing to file " + file, e);
+ Slog.e(TAG, "Error while writing to file " + file, e);
}
}
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/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index da33b44..d0cc122 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.content.pm.UserInfo;
@@ -39,6 +40,7 @@
import android.os.Environment;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
@@ -146,8 +148,9 @@
* </state>
* }</pre>
*/
+@SuppressLint("LongLogTag")
final class PersistentDataStore {
- private static final String LOG_TAG = CompanionDeviceManagerService.LOG_TAG + ".DataStore";
+ private static final String TAG = "CompanionDevice_PersistentDataStore";
private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -208,10 +211,9 @@
void readStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
- Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
-
+ Slog.i(TAG, "Reading associations for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath());
+ if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
@@ -220,12 +222,12 @@
final AtomicFile readFrom;
final String rootTag;
if (!file.getBaseFile().exists()) {
- if (DEBUG) Slog.d(LOG_TAG, " > File does not exist -> Try to read legacy file");
+ if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file");
legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
- if (DEBUG) Slog.d(LOG_TAG, " > Legacy file=" + legacyBaseFile.getPath());
+ if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath());
if (!legacyBaseFile.exists()) {
- if (DEBUG) Slog.d(LOG_TAG, " > Legacy file does not exist -> Abort");
+ if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort");
return;
}
@@ -236,13 +238,13 @@
rootTag = XML_TAG_STATE;
}
- if (DEBUG) Slog.d(LOG_TAG, " > Reading associations...");
+ if (DEBUG) Log.d(TAG, " > Reading associations...");
final int version = readStateFromFileLocked(userId, readFrom, rootTag,
associationsOut, previouslyUsedIdsPerPackageOut);
if (DEBUG) {
- Slog.d(LOG_TAG, " > Done reading: " + associationsOut);
+ Log.d(TAG, " > Done reading: " + associationsOut);
if (version < CURRENT_PERSISTENCE_VERSION) {
- Slog.d(LOG_TAG, " > File used old format: v." + version + " -> Re-write");
+ Log.d(TAG, " > File used old format: v." + version + " -> Re-write");
}
}
@@ -250,13 +252,13 @@
// The data is either in the legacy file or in the legacy format, or both.
// Save the data to right file in using the current format.
if (DEBUG) {
- Slog.d(LOG_TAG, " > Writing the data to " + file.getBaseFile().getPath());
+ Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath());
}
persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
if (legacyBaseFile != null) {
// We saved the data to the right file, can delete the old file now.
- if (DEBUG) Slog.d(LOG_TAG, " > Deleting legacy file");
+ if (DEBUG) Log.d(TAG, " > Deleting legacy file");
legacyBaseFile.delete();
}
}
@@ -273,11 +275,11 @@
void persistStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associations,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
- Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk");
- if (DEBUG) Slog.d(LOG_TAG, " > " + associations);
+ Slog.i(TAG, "Writing associations for user " + userId + " to disk");
+ if (DEBUG) Slog.d(TAG, " > " + associations);
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Slog.d(LOG_TAG, " > File=" + file.getBaseFile().getPath());
+ if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
synchronized (file) {
@@ -312,7 +314,7 @@
}
return version;
} catch (XmlPullParserException | IOException e) {
- Slog.e(LOG_TAG, "Error while reading associations file", e);
+ Slog.e(TAG, "Error while reading associations file", e);
return -1;
}
}
@@ -528,7 +530,7 @@
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
} catch (Exception e) {
- if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e);
+ if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
return associationInfo;
}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 76340fc..904283f 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -19,20 +19,23 @@
import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.content.Context;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import java.util.List;
/** Utility methods for accessing {@link RoleManager} APIs. */
+@SuppressLint("LongLogTag")
final class RolesUtils {
+ private static final String TAG = CompanionDeviceManagerService.LOG_TAG;
static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
@NonNull String packageName, @NonNull String role) {
@@ -45,7 +48,7 @@
static void addRoleHolderForAssociation(
@NonNull Context context, @NonNull AssociationInfo associationInfo) {
if (DEBUG) {
- Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
+ Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
}
final String deviceProfile = associationInfo.getDeviceProfile();
@@ -61,7 +64,7 @@
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
if (!success) {
- Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName
+ Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName
+ " to the list of " + deviceProfile + " holders.");
}
});
@@ -70,7 +73,7 @@
static void removeRoleHolderForAssociation(
@NonNull Context context, @NonNull AssociationInfo associationInfo) {
if (DEBUG) {
- Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
+ Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
}
final String deviceProfile = associationInfo.getDeviceProfile();
@@ -86,7 +89,7 @@
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
if (!success) {
- Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName
+ Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName
+ " from the list of " + deviceProfile + " holders.");
}
});
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 0eb6b8d..a771e7b 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -21,8 +21,6 @@
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothAdapter.nameForState;
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
@@ -35,6 +33,7 @@
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
@@ -72,7 +71,6 @@
@SuppressLint("LongLogTag")
class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
/**
@@ -156,7 +154,7 @@
private void checkBleState() {
enforceInitialized();
- final boolean bleAvailable = isBleAvailable();
+ final boolean bleAvailable = mBtAdapter.isLeEnabled();
if (DEBUG) {
Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
}
@@ -183,16 +181,6 @@
}
}
- /**
- * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
- * access level, so it's not accessible from here.
- */
- private boolean isBleAvailable() {
- final int state = mBtAdapter.getLeState();
- if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
- return state == STATE_ON || state == STATE_BLE_ON;
- }
-
@MainThread
private void startScan() {
enforceInitialized();
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index dbe866b..1ba198a 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,7 @@
package com.android.server.companion.presence;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
import android.annotation.NonNull;
@@ -39,7 +40,6 @@
class BluetoothCompanionDeviceConnectionListener
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BT";
interface Callback {
@@ -91,7 +91,7 @@
*/
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
- @DisconnectReason int reason) {
+ int reason) {
if (DEBUG) {
Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
Log.d(TAG, " reason=" + disconnectReasonText(reason));
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
new file mode 100644
index 0000000..6371b25
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.companion.presence;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.server.companion.AssociationStore;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
+ * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CompanionDevice_PresenceMonitor";
+
+ /** Callback for notifying about changes to status of companion devices. */
+ public interface Callback {
+ /** Invoked when companion device is found nearby or connects. */
+ void onDeviceAppeared(int associationId);
+
+ /** Invoked when a companion device no longer seen nearby or disconnects. */
+ void onDeviceDisappeared(int associationId);
+ }
+
+ private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull Callback mCallback;
+ private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ private final @NonNull BleCompanionDeviceScanner mBleScanner;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+
+ public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
+ @NonNull Callback callback) {
+ mAssociationStore = associationStore;
+ mCallback = callback;
+
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ }
+
+ /** Initialize {@link CompanionDevicePresenceMonitor} */
+ public void init(Context context) {
+ if (DEBUG) Log.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Log.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerListener(this);
+ }
+
+ /**
+ * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+ * or devices is connected (for Bluetooth); or reported (by the application) to be
+ * nearby (for "self-managed" associations).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported");
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+ }
+
+ @Override
+ public void onBleCompanionDeviceFound(int associationId) {
+ onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+ }
+
+ private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
+ int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
+ if (DEBUG) {
+ Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
+ + ", source=" + sourceLoggingTag);
+ Log.d(TAG, " > association="
+ + mAssociationStore.getAssociationById(newDeviceAssociationId));
+ }
+
+ final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
+ if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
+
+ final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
+ if (DEBUG && !added) {
+ Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
+ + "present by this source (" + sourceLoggingTag + ")");
+ }
+
+ if (alreadyPresent) return;
+
+ mCallback.onDeviceAppeared(newDeviceAssociationId);
+ }
+
+ private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource,
+ int goneDeviceAssociationId, @NonNull String sourceLoggingTag) {
+ if (DEBUG) {
+ Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
+ + ", source=" + sourceLoggingTag);
+ Log.d(TAG, " > association="
+ + mAssociationStore.getAssociationById(goneDeviceAssociationId));
+ }
+
+ final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
+ if (!removed) {
+ if (DEBUG) {
+ Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+ + "as present by this source (" + sourceLoggingTag + ")");
+ }
+ return;
+ }
+
+ final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
+ if (stillPresent) {
+ if (DEBUG) Log.i(TAG, " Device is still present.");
+ return;
+ }
+
+ mCallback.onDeviceDisappeared(goneDeviceAssociationId);
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 6c56e2f..e6bfd1f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -18,8 +18,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -48,6 +51,20 @@
private static final String TAG = "VirtualInputController";
+ private static final AtomicLong sNextPhysId = new AtomicLong(1);
+
+ static final String PHYS_TYPE_KEYBOARD = "Keyboard";
+ static final String PHYS_TYPE_MOUSE = "Mouse";
+ static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+ @StringDef(prefix = { "PHYS_TYPE_" }, value = {
+ PHYS_TYPE_KEYBOARD,
+ PHYS_TYPE_MOUSE,
+ PHYS_TYPE_TOUCHSCREEN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PhysType {
+ }
+
private final Object mLock;
/* Token -> file descriptor associations. */
@@ -56,6 +73,8 @@
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
private final NativeWrapper mNativeWrapper;
+ private final DisplayManagerInternal mDisplayManagerInternal;
+ private final InputManagerInternal mInputManagerInternal;
/**
* Because the pointer is a singleton, it can only be targeted at one display at a time. Because
@@ -73,6 +92,8 @@
mLock = lock;
mNativeWrapper = nativeWrapper;
mActivePointerDisplayId = Display.INVALID_DISPLAY;
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
}
void close() {
@@ -90,7 +111,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_KEYBOARD);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating keyboard: " + -fd);
@@ -99,7 +122,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
+ InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -114,7 +137,9 @@
int productId,
@NonNull IBinder deviceToken,
int displayId) {
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+ final String phys = createPhys(PHYS_TYPE_MOUSE);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
if (fd < 0) {
throw new RuntimeException(
"A native error occurred when creating mouse: " + -fd);
@@ -123,11 +148,9 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId));
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- inputManagerInternal.setPointerAcceleration(1);
+ InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ mInputManagerInternal.setPointerAcceleration(1);
mActivePointerDisplayId = displayId;
}
try {
@@ -144,7 +167,9 @@
@NonNull IBinder deviceToken,
int displayId,
@NonNull Point screenSize) {
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
+ setUniqueIdAssociation(displayId, phys);
+ final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
screenSize.y, screenSize.x);
if (fd < 0) {
throw new RuntimeException(
@@ -154,7 +179,7 @@
synchronized (mLock) {
mInputDeviceDescriptors.put(deviceToken,
new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
+ InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
}
try {
deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
@@ -174,6 +199,7 @@
}
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+ InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
// Reset values to the default if all virtual mice are unregistered, or set display
// id if there's another mouse (choose the most recent).
@@ -197,9 +223,7 @@
}
}
if (mostRecentlyCreatedMouse != null) {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(
mostRecentlyCreatedMouse.getDisplayId());
mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
} else {
@@ -209,14 +233,21 @@
}
private void resetMouseValuesLocked() {
- final InputManagerInternal inputManagerInternal =
- LocalServices.getService(InputManagerInternal.class);
- inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
- inputManagerInternal.setPointerAcceleration(
+ mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ mInputManagerInternal.setPointerAcceleration(
IInputConstants.DEFAULT_POINTER_ACCELERATION);
mActivePointerDisplayId = Display.INVALID_DISPLAY;
}
+ private static String createPhys(@PhysType String type) {
+ return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+ }
+
+ private void setUniqueIdAssociation(int displayId, String phys) {
+ final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
+ InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
+ }
+
boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
synchronized (mLock) {
final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
@@ -321,17 +352,18 @@
fout.println(" creationOrder: "
+ inputDeviceDescriptor.getCreationOrderNumber());
fout.println(" type: " + inputDeviceDescriptor.getType());
+ fout.println(" phys: " + inputDeviceDescriptor.getPhys());
}
fout.println(" Active mouse display id: " + mActivePointerDisplayId);
}
}
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
- int productId);
- private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
- int productId);
+ int productId, String phys);
+ private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
+ String phys);
private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
- int productId, int height, int width);
+ int productId, String phys, int height, int width);
private static native boolean nativeCloseUinput(int fd);
private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
@@ -345,20 +377,18 @@
/** Wrapper around the static native methods for tests. */
@VisibleForTesting
protected static class NativeWrapper {
- public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputKeyboard(deviceName, vendorId,
- productId);
+ public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
}
- public int openUinputMouse(String deviceName, int vendorId, int productId) {
- return nativeOpenUinputMouse(deviceName, vendorId,
- productId);
+ public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
+ return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
}
- public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
- int width) {
- return nativeOpenUinputTouchscreen(deviceName, vendorId,
- productId, height, width);
+ public int openUinputTouchscreen(String deviceName, int vendorId,
+ int productId, String phys, int height, int width) {
+ return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
+ width);
}
public boolean closeUinput(int fd) {
@@ -410,15 +440,17 @@
private final IBinder.DeathRecipient mDeathRecipient;
private final @Type int mType;
private final int mDisplayId;
+ private final String mPhys;
// Monotonically increasing number; devices with lower numbers were created earlier.
private final long mCreationOrderNumber;
- InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
- @Type int type, int displayId) {
+ InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
+ int displayId, String phys) {
mFd = fd;
mDeathRecipient = deathRecipient;
mType = type;
mDisplayId = displayId;
+ mPhys = phys;
mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
}
@@ -445,6 +477,10 @@
public long getCreationOrderNumber() {
return mCreationOrderNumber;
}
+
+ public String getPhys() {
+ return mPhys;
+ }
}
private final class BinderDeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1106fe7..561009f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -134,6 +134,7 @@
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-permission.stubs.system_server",
+ "service-supplementalprocess.stubs.system_server",
],
required: [
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 435d294..a35aa7c 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.LocusId;
@@ -375,10 +376,11 @@
* to this broadcast.
* @param timestampMs time (in millis) when the broadcast was dispatched, in
* {@link SystemClock#elapsedRealtime()} timebase.
+ * @param targetUidProcState process state of the uid that the broadcast is targeted to.
*/
public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
@NonNull UserHandle targetUser, long idForResponseEvent,
- @ElapsedRealtimeLong long timestampMs);
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState);
/**
* Reports a notification posted event to the UsageStatsManager.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index f56bfab..a8eeaf8 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -901,32 +901,20 @@
/**
* Perform the given action for each package.
- *
- * @param locked whether to hold the packages lock. If the lock is not held, the objects will
- * be iterated using a temporary data structure. In the vast majority of cases,
- * the lock should not have to be held. This is exposed to mirror the
- * functionality of the other forEach methods, for eventual migration.
* @param action action to be performed
*/
- public abstract void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action);
+ public abstract void forEachPackageState(Consumer<PackageStateInternal> action);
/**
- * {@link #forEachPackageState(boolean, Consumer)} but filtered to only states with packages
+ * {@link #forEachPackageState(Consumer)} but filtered to only states with packages
* on device where {@link PackageStateInternal#getPkg()} is not null.
*/
public abstract void forEachPackage(Consumer<AndroidPackage> action);
/**
* Perform the given action for each installed package for a user.
- * Note that packages lock will be held while performing the actions.
*/
public abstract void forEachInstalledPackage(
- @NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId);
-
- /**
- * Perform the given action for each installed package for a user.
- */
- public abstract void forEachInstalledPackage(boolean locked,
@NonNull Consumer<AndroidPackage> action, @UserIdInt int userId);
/** Returns the list of enabled components */
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..7714dbc
--- /dev/null
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.internal.util.FrameworkStatsLog;
+
+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));
+ FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, 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);
+ }
+
+ if (packageInfo.isApex) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ mBinaryHashes.get(packageInfo.packageName));
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Could not find package with name " + packageName);
+ continue;
+ }
+ }
+
+ return true;
+ }
+
+ private 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);
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName,
+ packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName));
+ Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
+ packageInfo.lastUpdateTime));
+ mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
+ }
+
+ // 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/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index e5a7b4e..8a6b54f 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,7 +19,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -32,15 +31,21 @@
import android.os.UEventObserver;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.server.ExtconUEventObserver.ExtconInfo;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* DockObserver monitors for a docking station.
@@ -48,9 +53,6 @@
final class DockObserver extends SystemService {
private static final String TAG = "DockObserver";
- private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
- private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
-
private static final int MSG_DOCK_STATE_CHANGED = 0;
private final PowerManager mPowerManager;
@@ -69,6 +71,92 @@
private final boolean mAllowTheaterModeWakeFromDock;
+ private final List<ExtconStateConfig> mExtconStateConfigs;
+
+ static final class ExtconStateProvider {
+ private final Map<String, String> mState;
+
+ ExtconStateProvider(Map<String, String> state) {
+ mState = state;
+ }
+
+ String getValue(String key) {
+ return mState.get(key);
+ }
+
+
+ static ExtconStateProvider fromString(String stateString) {
+ Map<String, String> states = new HashMap<>();
+ String[] lines = stateString.split("\n");
+ for (String line : lines) {
+ String[] fields = line.split("=");
+ if (fields.length == 2) {
+ states.put(fields[0], fields[1]);
+ } else {
+ Slog.e(TAG, "Invalid line: " + line);
+ }
+ }
+ return new ExtconStateProvider(states);
+ }
+
+ static ExtconStateProvider fromFile(String stateFilePath) {
+ char[] buffer = new char[1024];
+ try (FileReader file = new FileReader(stateFilePath)) {
+ int len = file.read(buffer, 0, 1024);
+ String stateString = (new String(buffer, 0, len)).trim();
+ return ExtconStateProvider.fromString(stateString);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "No state file found at: " + stateFilePath);
+ return new ExtconStateProvider(new HashMap<>());
+ } catch (Exception e) {
+ Slog.e(TAG, "" , e);
+ return new ExtconStateProvider(new HashMap<>());
+ }
+ }
+ }
+
+ /**
+ * Represents a mapping from extcon state to EXTRA_DOCK_STATE value. Each
+ * instance corresponds to an entry in config_dockExtconStateMapping.
+ */
+ private static final class ExtconStateConfig {
+
+ // The EXTRA_DOCK_STATE that will be used if the extcon key-value pairs match
+ public final int extraStateValue;
+
+ // A list of key-value pairs that must be present in the extcon state for a match
+ // to be considered. An empty list is considered a matching wildcard.
+ public final List<Pair<String, String>> keyValuePairs = new ArrayList<>();
+
+ ExtconStateConfig(int extraStateValue) {
+ this.extraStateValue = extraStateValue;
+ }
+ }
+
+ private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
+ String[] rows = context.getResources().getStringArray(
+ com.android.internal.R.array.config_dockExtconStateMapping);
+ try {
+ ArrayList<ExtconStateConfig> configs = new ArrayList<>();
+ for (String row : rows) {
+ String[] rowFields = row.split(",");
+ ExtconStateConfig config = new ExtconStateConfig(Integer.parseInt(rowFields[0]));
+ for (int i = 1; i < rowFields.length; i++) {
+ String[] keyValueFields = rowFields[i].split("=");
+ if (keyValueFields.length != 2) {
+ throw new IllegalArgumentException("Invalid key-value: " + rowFields[i]);
+ }
+ config.keyValuePairs.add(Pair.create(keyValueFields[0], keyValueFields[1]));
+ }
+ configs.add(config);
+ }
+ return configs;
+ } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
+ Slog.e(TAG, "Could not parse extcon state config", e);
+ return new ArrayList<>();
+ }
+ }
+
public DockObserver(Context context) {
super(context);
@@ -77,9 +165,25 @@
mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
- init(); // set initial status
+ mExtconStateConfigs = loadExtconStateConfigs(context);
- mObserver.startObserving(DOCK_UEVENT_MATCH);
+ List<ExtconInfo> infos = ExtconInfo.getExtconInfoForTypes(new String[] {
+ ExtconInfo.EXTCON_DOCK
+ });
+
+ if (!infos.isEmpty()) {
+ ExtconInfo info = infos.get(0);
+ Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
+ + ", statePath: " + info.getStatePath());
+
+ // set initial status
+ setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+ mPreviousDockState = mActualDockState;
+
+ mExtconUEventObserver.startObserving(info);
+ } else {
+ Slog.i(TAG, "No extcon dock device found in this kernel.");
+ }
}
@Override
@@ -101,26 +205,6 @@
}
}
- private void init() {
- synchronized (mLock) {
- try {
- char[] buffer = new char[1024];
- FileReader file = new FileReader(DOCK_STATE_PATH);
- try {
- int len = file.read(buffer, 0, 1024);
- setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim()));
- mPreviousDockState = mActualDockState;
- } finally {
- file.close();
- }
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have dock station support");
- } catch (Exception e) {
- Slog.e(TAG, "" , e);
- }
- }
- }
-
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -234,19 +318,50 @@
}
};
- private final UEventObserver mObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Dock UEVENT: " + event.toString());
+ private int getDockedStateExtraValue(ExtconStateProvider state) {
+ for (ExtconStateConfig config : mExtconStateConfigs) {
+ boolean match = true;
+ for (Pair<String, String> keyValue : config.keyValuePairs) {
+ String stateValue = state.getValue(keyValue.first);
+ match = match && keyValue.second.equals(stateValue);
+ if (!match) {
+ break;
+ }
}
- try {
- synchronized (mLock) {
- setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
+ if (match) {
+ return config.extraStateValue;
+ }
+ }
+
+ return Intent.EXTRA_DOCK_STATE_DESK;
+ }
+
+ @VisibleForTesting
+ void setDockStateFromProviderForTesting(ExtconStateProvider provider) {
+ synchronized (mLock) {
+ setDockStateFromProviderLocked(provider);
+ }
+ }
+
+ private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
+ int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ if ("1".equals(provider.getValue("DOCK"))) {
+ state = getDockedStateExtraValue(provider);
+ }
+ setActualDockStateLocked(state);
+ }
+
+ private final ExtconUEventObserver mExtconUEventObserver = new ExtconUEventObserver() {
+ @Override
+ public void onUEvent(ExtconInfo extconInfo, UEventObserver.UEvent event) {
+ synchronized (mLock) {
+ String stateString = event.get("STATE");
+ if (stateString != null) {
+ setDockStateFromProviderLocked(ExtconStateProvider.fromString(stateString));
+ } else {
+ Slog.e(TAG, "Extcon event missing STATE: " + event);
}
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Could not parse switch state from event " + event);
}
}
};
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/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 16645df..06c11fa 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -673,6 +673,12 @@
throw new UnsupportedOperationException("cannot read frp credential");
}
}
+
+ @Override
+ public String getPersistentDataPackageName() {
+ enforcePersistentDataBlockAccess();
+ return mContext.getString(R.string.config_persistentDataPackageName);
+ }
};
private PersistentDataBlockManagerInternal mInternalService =
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f71f02a..8aeae6a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -123,6 +123,7 @@
import android.provider.Downloads;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.service.storage.ExternalStorageService;
import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -491,6 +492,8 @@
@GuardedBy("mAppFuseLock")
private AppFuseBridge mAppFuseBridge = null;
+ private HashMap<Integer, Integer> mUserSharesMediaWith = new HashMap<>();
+
/** Matches known application dir paths. The first group contains the generic part of the path,
* the second group contains the user id (or null if it's a public volume without users), the
* third group contains the package name, and the fourth group the remainder of the path.
@@ -1235,6 +1238,21 @@
private void onUnlockUser(int userId) {
Slog.d(TAG, "onUnlockUser " + userId);
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Check if this user shares media with another user
+ try {
+ Context userContext = mContext.createPackageContextAsUser("system", 0,
+ UserHandle.of(userId));
+ UserManager um = userContext.getSystemService(UserManager.class);
+ if (um != null && um.isMediaSharedWithParent()) {
+ int parentUserId = um.getProfileParent(userId).id;
+ mUserSharesMediaWith.put(userId, parentUserId);
+ mUserSharesMediaWith.put(parentUserId, userId);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to create user context for user " + userId);
+ }
+ }
// We purposefully block here to make sure that user-specific
// staging area is ready so it's ready for zygote-forked apps to
// bind mount against.
@@ -3971,6 +3989,29 @@
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
+ final boolean includeSharedProfile =
+ (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0;
+
+ // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile
+ if (includeSharedProfile) {
+ try {
+ // Get package name for calling app and
+ // verify it has MANAGE_EXTERNAL_STORAGE permission
+ final String[] packagesFromUid = mIPackageManager.getPackagesForUid(callingUid);
+ if (packagesFromUid == null) {
+ throw new SecurityException("Unknown uid " + callingUid);
+ }
+ // Checking first entry in packagesFromUid is enough as using "sharedUserId"
+ // mechanism is rare and discouraged. Also, Apps that share same UID share the same
+ // permissions.
+ if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid,
+ packagesFromUid[0])) {
+ throw new SecurityException("Only File Manager Apps permitted");
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown uid " + callingUid, re);
+ }
+ }
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
@@ -4002,6 +4043,7 @@
final ArrayList<StorageVolume> res = new ArrayList<>();
final ArraySet<String> resUuids = new ArraySet<>();
+ final int userIdSharingMedia = mUserSharesMediaWith.getOrDefault(userId, -1);
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final String volId = mVolumes.keyAt(i);
@@ -4014,6 +4056,11 @@
if (vol.getMountUserId() == userId) {
break;
}
+ if (includeSharedProfile && vol.getMountUserId() == userIdSharingMedia) {
+ // If the volume belongs to a user we share media with,
+ // return it too.
+ break;
+ }
// Skip if emulated volume not for userId
default:
continue;
@@ -4021,10 +4068,12 @@
boolean match = false;
if (forWrite) {
- match = vol.isVisibleForWrite(userId);
+ match = vol.isVisibleForWrite(userId)
+ || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia));
} else {
match = vol.isVisibleForUser(userId)
- || (includeInvisible && vol.getPath() != null);
+ || (includeInvisible && vol.getPath() != null)
+ || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia));
}
if (!match) continue;
@@ -4045,9 +4094,13 @@
reportUnmounted = true;
}
- final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
+ int volUserId = userId;
+ if (volUserId != vol.getMountUserId() && vol.getMountUserId() >= 0) {
+ volUserId = vol.getMountUserId();
+ }
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, volUserId,
reportUnmounted);
- if (vol.isPrimary()) {
+ if (vol.isPrimary() && vol.getMountUserId() == userId) {
res.add(0, userVol);
foundPrimary = true;
} else {
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 0bc3fcc..d719d77 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -509,8 +509,7 @@
throw new IllegalArgumentException(onWhat + " what?");
}
} catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
- + " to service " + serviceName, ex);
+ logFailure(onWhat, curUser, serviceName, ex);
}
if (!submitToThreadPool) {
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
@@ -576,19 +575,19 @@
return () -> {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace);
final String serviceName = service.getClass().getName();
+ final int curUserId = curUser.getUserIdentifier();
+ t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
try {
- final int curUserId = curUser.getUserIdentifier();
- t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName);
long time = SystemClock.elapsedRealtime();
service.onUserStarting(curUser);
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
"on" + USER_STARTING + "User-" + curUserId);
- t.traceEnd();
} catch (Exception e) {
- Slog.wtf(TAG, "Failure reporting " + USER_STARTING + " of user " + curUser
- + " to service " + serviceName, e);
+ logFailure(USER_STARTING, curUser, serviceName, e);
Slog.e(TAG, "Disabling thread pool - please capture a bug report.");
sUseLifecycleThreadPool = false;
+ } finally {
+ t.traceEnd();
}
};
}
@@ -601,14 +600,26 @@
final int curUserId = curUser.getUserIdentifier();
t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId
+ "_" + eventType + "_" + serviceName);
- long time = SystemClock.elapsedRealtime();
- service.onUserCompletedEvent(curUser, eventType);
- warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
- "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
- t.traceEnd();
+ try {
+ long time = SystemClock.elapsedRealtime();
+ service.onUserCompletedEvent(curUser, eventType);
+ warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
+ "on" + USER_COMPLETED_EVENT + "User-" + curUserId);
+ } catch (Exception e) {
+ logFailure(USER_COMPLETED_EVENT, curUser, serviceName, e);
+ throw e;
+ } finally {
+ t.traceEnd();
+ }
};
}
+ /** Logs the failure. That's all. Tests may rely on parsing it, so only modify carefully. */
+ private void logFailure(String onWhat, TargetUser curUser, String serviceName, Exception ex) {
+ Slog.wtf(TAG, "SystemService failure: Failure reporting " + onWhat + " of user "
+ + curUser + " to service " + serviceName, ex);
+ }
+
/** Sets the safe mode flag for services to query. */
void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 6a7afd9..2d328d8 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -539,7 +539,13 @@
@GuardedBy("mLock")
private void notifyAllPolicyListenersLocked() {
for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
- Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged());
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ policyListener.mListener.onPolicyChanged();
+ } catch (RemoteException e) {
+ logDbg("VcnStatusCallback threw on VCN status change", e);
+ }
+ });
}
}
@@ -548,8 +554,13 @@
@NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
if (isCallbackPermissioned(cbInfo, subGroup)) {
- Binder.withCleanCallingIdentity(
- () -> cbInfo.mCallback.onVcnStatusChanged(statusCode));
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ cbInfo.mCallback.onVcnStatusChanged(statusCode);
+ } catch (RemoteException e) {
+ logDbg("VcnStatusCallback threw on VCN status change", e);
+ }
+ });
}
}
}
@@ -1222,13 +1233,17 @@
// Notify all registered StatusCallbacks for this subGroup
for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
if (isCallbackPermissioned(cbInfo, mSubGroup)) {
- Binder.withCleanCallingIdentity(
- () ->
- cbInfo.mCallback.onGatewayConnectionError(
- gatewayConnectionName,
- errorCode,
- exceptionClass,
- exceptionMessage));
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ cbInfo.mCallback.onGatewayConnectionError(
+ gatewayConnectionName,
+ errorCode,
+ exceptionClass,
+ exceptionMessage);
+ } catch (RemoteException e) {
+ logDbg("VcnStatusCallback threw on VCN status change", e);
+ }
+ });
}
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8887108..9353dd8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2721,7 +2721,8 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
- String instanceName, String callingPackage, final int userId)
+ String instanceName, boolean isSupplementalProcessService, String callingPackage,
+ final int userId)
throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
@@ -2805,10 +2806,9 @@
final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
- ServiceLookupResult res =
- retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
- callingPid, callingUid, userId, true,
- callerFg, isBindExternal, allowInstant);
+ ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
+ isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid,
+ userId, true, callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -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)) {
@@ -3216,6 +3228,20 @@
int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
+ return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage,
+ callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
+ allowInstant);
+ }
+
+ private ServiceLookupResult retrieveServiceLocked(Intent service,
+ String instanceName, boolean isSupplementalProcessService, String resolvedType,
+ String callingPackage, int callingPid, int callingUid, int userId,
+ boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
+ boolean allowInstant) {
+ if (isSupplementalProcessService && instanceName == null) {
+ throw new IllegalArgumentException("No instanceName provided for supplemental process");
+ }
+
ServiceRecord r = null;
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
@@ -3237,7 +3263,6 @@
if (instanceName == null) {
comp = service.getComponent();
} else {
- // This is for isolated services
final ComponentName realComp = service.getComponent();
if (realComp == null) {
throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
@@ -3292,12 +3317,19 @@
return null;
}
if (instanceName != null
- && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0
+ && !isSupplementalProcessService) {
throw new IllegalArgumentException("Can't use instance name '" + instanceName
- + "' with non-isolated service '" + sInfo.name + "'");
+ + "' with non-isolated non-supplemental service '" + sInfo.name + "'");
}
- ComponentName className = new ComponentName(
- sInfo.applicationInfo.packageName, sInfo.name);
+ if (isSupplementalProcessService
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+ throw new IllegalArgumentException("Service cannot be both supplemental and "
+ + "isolated");
+ }
+
+ ComponentName className = new ComponentName(sInfo.applicationInfo.packageName,
+ sInfo.name);
ComponentName name = comp != null ? comp : className;
if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid,
name.getPackageName(), sInfo.applicationInfo.uid)) {
@@ -3380,7 +3412,8 @@
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
r = new ServiceRecord(mAm, className, name, definingPackageName,
- definingUid, filter, sInfo, callingFromFg, res);
+ definingUid, filter, sInfo, callingFromFg, res,
+ isSupplementalProcessService);
res.setService(r);
smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
@@ -5138,29 +5171,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/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index bcb1be3..940ad73 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -123,6 +123,7 @@
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
"kill_bg_restricted_cached_idle_settle_time";
+ static final String KEY_ENABLE_COMPONENT_ALIAS = "enable_experimental_component_alias";
static final String KEY_COMPONENT_ALIAS_OVERRIDES = "component_alias_overrides";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
@@ -199,6 +200,7 @@
* Whether or not to enable the extra delays to service restarts on memory pressure.
*/
private static final boolean DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE = true;
+ private static final boolean DEFAULT_ENABLE_COMPONENT_ALIAS = false;
private static final String DEFAULT_COMPONENT_ALIAS_OVERRIDES = "";
// Flag stored in the DeviceConfig API.
@@ -595,6 +597,12 @@
DEFAULT_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE;
/**
+ * Whether to enable "component alias" experimental feature. This can only be enabled
+ * on userdebug or eng builds.
+ */
+ volatile boolean mEnableComponentAlias = DEFAULT_ENABLE_COMPONENT_ALIAS;
+
+ /**
* Defines component aliases. Format
* ComponentName ":" ComponentName ( "," ComponentName ":" ComponentName )*
*/
@@ -831,6 +839,7 @@
case KEY_ENABLE_EXTRA_SERVICE_RESTART_DELAY_ON_MEM_PRESSURE:
updateEnableExtraServiceRestartDelayOnMemPressure();
break;
+ case KEY_ENABLE_COMPONENT_ALIAS:
case KEY_COMPONENT_ALIAS_OVERRIDES:
updateComponentAliases();
break;
@@ -1269,11 +1278,15 @@
}
private void updateComponentAliases() {
+ mEnableComponentAlias = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_ENABLE_COMPONENT_ALIAS,
+ DEFAULT_ENABLE_COMPONENT_ALIAS);
mComponentAliasOverrides = DeviceConfig.getString(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_COMPONENT_ALIAS_OVERRIDES,
DEFAULT_COMPONENT_ALIAS_OVERRIDES);
- mService.mComponentAliasResolver.update(mComponentAliasOverrides);
+ mService.mComponentAliasResolver.update(mEnableComponentAlias, mComponentAliasOverrides);
}
private void updateProcessKillTimeout() {
@@ -1512,6 +1525,8 @@
pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
pw.print(" "); pw.print(KEY_FGS_ALLOW_OPT_OUT);
pw.print("="); pw.println(mFgsAllowOptOut);
+ pw.print(" "); pw.print(KEY_ENABLE_COMPONENT_ALIAS);
+ pw.print("="); pw.println(mEnableComponentAlias);
pw.print(" "); pw.print(KEY_COMPONENT_ALIAS_OVERRIDES);
pw.print("="); pw.println(mComponentAliasOverrides);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9a1bfdd..d9ee7d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.TransactionTooLargeException;
/**
* Interface for in-process calls into
@@ -58,4 +61,24 @@
* @hide
*/
void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
+
+ /**
+ * Starts a supplemental process service and binds to it. You can through the arguments here
+ * have the system bring up multiple concurrent processes hosting their own instance of that
+ * service. The <var>userAppUid</var> you provide here identifies the different instances - each
+ * unique uid is attributed to a supplemental process.
+ *
+ * @param service Identifies the supplemental process service to connect to. The Intent must
+ * specify an explicit component name. This value cannot be null.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service or if your client doesn't
+ * have permission to bind to it.
+ */
+ boolean startAndBindSupplementalProcessService(@NonNull Intent service,
+ @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException;
+
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f67e732..0310b0f 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;
@@ -215,6 +218,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.LocusId;
+import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
@@ -233,11 +237,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;
@@ -255,6 +257,7 @@
import android.os.BugreportParams;
import android.os.Build;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.Debug;
import android.os.DropBoxManager;
import android.os.FactoryTest;
@@ -335,7 +338,7 @@
import com.android.internal.app.SystemUserHomeActivity;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
@@ -396,6 +399,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 +443,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 +1379,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 +1481,8 @@
final UidObserverController mUidObserverController;
+ final AppRestrictionController mAppRestrictionController;
+
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
@@ -1512,6 +1539,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 +1858,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 +2306,7 @@
mPendingIntentController = hasHandlerThread
? new PendingIntentController(handlerThread.getLooper(), mUserController,
mConstants) : null;
+ mAppRestrictionController = new AppRestrictionController(mContext, this);
mProcStartHandlerThread = null;
mProcStartHandler = null;
mHiddenApiBlacklist = null;
@@ -2378,6 +2416,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"));
@@ -2861,16 +2901,31 @@
mActivityTaskManager.setPackageScreenCompatMode(packageName, mode);
}
- private boolean hasUsageStatsPermission(String callingPackage) {
+ private boolean hasUsageStatsPermission(String callingPackage, int callingUid, int callingPid) {
final int mode = mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS,
- Binder.getCallingUid(), callingPackage, null, false, "", false).getOpMode();
+ callingUid, callingPackage, null, false, "", false).getOpMode();
if (mode == AppOpsManager.MODE_DEFAULT) {
- return checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ return checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED;
}
return mode == AppOpsManager.MODE_ALLOWED;
}
+ private boolean hasUsageStatsPermission(String callingPackage) {
+ return hasUsageStatsPermission(callingPackage,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
+ private void enforceUsageStatsPermission(String callingPackage,
+ int callingUid, int callingPid, String operation) {
+ if (!hasUsageStatsPermission(callingPackage, callingUid, callingPid)) {
+ final String errorMsg = "Permission denial for <" + operation + "> from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " which requires PACKAGE_USAGE_STATS permission";
+ throw new SecurityException(errorMsg);
+ }
+ }
+
@Override
public int getPackageProcessState(String packageName, String callingPackage) {
if (!hasUsageStatsPermission(callingPackage)) {
@@ -3782,10 +3837,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 +3850,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 +4188,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 +4314,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,
@@ -4885,7 +4968,7 @@
// This line is needed to CTS test for the correct exception handling
// See b/138952436#comment36 for context
Slog.i(TAG, "About to commit checkpoint");
- IStorageManager storageManager = PackageHelper.getStorageManager();
+ IStorageManager storageManager = InstallLocationUtils.getStorageManager();
storageManager.commitChanges();
} catch (Exception e) {
PowerManager pm = (PowerManager)
@@ -4948,6 +5031,7 @@
}
// UART is on if init's console service is running, send a warning notification.
showConsoleNotificationIfActive();
+ showMteOverrideNotificationIfActive();
t.traceEnd();
}
@@ -4982,6 +5066,35 @@
}
+ private void showMteOverrideNotificationIfActive() {
+ if (!SystemProperties.getBoolean("ro.arm64.memtag.bootctl_supported", false)
+ || !com.android.internal.os.Zygote.nativeSupportsMemoryTagging()) {
+ return;
+ }
+ String title = mContext
+ .getString(com.android.internal.R.string.mte_override_notification_title);
+ String message = mContext
+ .getString(com.android.internal.R.string.mte_override_notification_message);
+ Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setOngoing(true)
+ .setTicker(title)
+ .setDefaults(0) // please be quiet
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .build();
+
+ NotificationManager notificationManager =
+ mContext.getSystemService(NotificationManager.class);
+ notificationManager.notifyAsUser(
+ null, SystemMessage.NOTE_MTE_OVERRIDE_ENABLED, notification, UserHandle.ALL);
+ }
+
@Override
public void bootAnimationComplete() {
if (DEBUG_ALL) Slog.d(TAG, "bootAnimationComplete: Callers=" + Debug.getCallers(4));
@@ -7722,6 +7835,7 @@
mUserController.onSystemReady();
mAppOpsService.systemReady();
mProcessList.onSystemReady();
+ mAppRestrictionController.onSystemReady();
mSystemReady = true;
t.traceEnd();
}
@@ -7952,7 +8066,8 @@
// Load the component aliases.
t.traceBegin("componentAlias");
- mComponentAliasResolver.onSystemReady(mConstants.mComponentAliasOverrides);
+ mComponentAliasResolver.onSystemReady(mConstants.mEnableComponentAlias,
+ mConstants.mComponentAliasOverrides);
t.traceEnd(); // componentAlias
t.traceEnd(); // PhaseActivityManagerReady
@@ -9005,6 +9120,10 @@
}
mComponentAliasResolver.dump(pw);
}
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ mAppRestrictionController.dump(pw, "");
+ }
}
/**
@@ -12213,13 +12332,25 @@
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags,
String callingPackage, int userId) throws TransactionTooLargeException {
- return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+ return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
null, callingPackage, userId);
}
- public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+ /**
+ * Binds to a service with a given instanceName, creating it if it does not already exist.
+ * If the instanceName field is not supplied, binding to the service occurs as usual.
+ */
+ public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String instanceName,
String callingPackage, int userId) throws TransactionTooLargeException {
+ return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
+ instanceName, false, callingPackage, userId);
+ }
+
+ private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, String instanceName,
+ boolean isSupplementalProcessService, String callingPackage, int userId)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -12231,6 +12362,10 @@
throw new IllegalArgumentException("callingPackage cannot be null");
}
+ if (isSupplementalProcessService && instanceName == null) {
+ throw new IllegalArgumentException("No instance name provided for isolated process");
+ }
+
// Ensure that instanceName, which is caller provided, does not contain
// unusual characters.
if (instanceName != null) {
@@ -12244,8 +12379,8 @@
}
synchronized(this) {
- return mServices.bindServiceLocked(caller, token, service,
- resolvedType, connection, flags, instanceName, callingPackage, userId);
+ return mServices.bindServiceLocked(caller, token, service, resolvedType, connection,
+ flags, instanceName, isSupplementalProcessService, callingPackage, userId);
}
}
@@ -12664,7 +12799,7 @@
noAction.add(null);
actions = noAction.iterator();
}
- boolean onlyProtectedBroadcasts = actions.hasNext();
+ boolean onlyProtectedBroadcasts = true;
// Collect stickies of users and check if broadcast is only registered for protected
// broadcasts
@@ -12738,6 +12873,8 @@
// Change is not enabled, thus not targeting T+. Assume exported.
flags |= Context.RECEIVER_EXPORTED;
}
+ } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+ flags |= Context.RECEIVER_EXPORTED;
}
}
@@ -13230,6 +13367,13 @@
backgroundActivityStartsToken = null;
}
}
+
+ // TODO (206518114): We need to use the "real" package name which sent the broadcast,
+ // in case the broadcast is sent via PendingIntent.
+ if (brOptions.getIdForResponseEvent() > 0) {
+ enforceUsageStatsPermission(callerPackage, realCallingUid, realCallingPid,
+ "recordResponseEventWhileInBackground()");
+ }
}
// Verify that protected broadcasts are only being sent by system code,
@@ -14807,8 +14951,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);
@@ -15369,6 +15521,62 @@
}
}
+ /**
+ * Dump the resources structure for the given process
+ *
+ * @param process The process to dump resource info for
+ * @param fd The FileDescriptor to dump it into
+ * @throws RemoteException
+ */
+ public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback)
+ throws RemoteException {
+ synchronized (this) {
+ ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources");
+ IApplicationThread thread;
+ if (proc == null || (thread = proc.getThread()) == null) {
+ throw new IllegalArgumentException("Unknown process: " + process);
+ }
+ thread.dumpResources(fd, callback);
+ return true;
+ }
+ }
+
+ /**
+ * Dump the resources structure for all processes
+ *
+ * @param fd The FileDescriptor to dump it into
+ * @throws RemoteException
+ */
+ public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException {
+ synchronized (mProcLock) {
+ mProcessList.forEachLruProcessesLOSP(true, app -> {
+ ConditionVariable lock = new ConditionVariable();
+ RemoteCallback
+ finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+ pw.println(String.format("------ DUMP RESOURCES %s (%s) ------",
+ app.processName,
+ app.info.packageName));
+ pw.flush();
+ try {
+ app.getThread().dumpResources(fd.dup(), finishCallback);
+ lock.block(2000);
+ } catch (Exception e) {
+ pw.println(String.format(
+ "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------",
+ app.processName,
+ app.info.packageName,
+ e.getMessage()));
+ pw.flush();
+ }
+ pw.println(String.format("------ END DUMP RESOURCES %s (%s) ------",
+ app.processName,
+ app.info.packageName));
+ pw.flush();
+ });
+ }
+ }
+
@Override
public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
String reportPackage) {
@@ -15714,6 +15922,34 @@
}
@Override
+ public boolean startAndBindSupplementalProcessService(Intent service,
+ ServiceConnection conn, int userAppUid) throws TransactionTooLargeException {
+ if (service == null) {
+ throw new IllegalArgumentException("intent is null");
+ }
+ if (conn == null) {
+ throw new IllegalArgumentException("connection is null");
+ }
+ if (service.getComponent() == null) {
+ throw new IllegalArgumentException("service must specify explicit component");
+ }
+ if (!UserHandle.isApp(userAppUid)) {
+ throw new IllegalArgumentException("uid is not within application range");
+ }
+
+ Handler handler = mContext.getMainThreadHandler();
+ int flags = Context.BIND_AUTO_CREATE;
+
+ final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags);
+ service.prepareToLeaveProcess(mContext);
+ return ActivityManagerService.this.bindServiceInstance(
+ mContext.getIApplicationThread(), mContext.getActivityToken(), service,
+ service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
+ Integer.toString(userAppUid), /*isSupplementalProcessService*/ true,
+ mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+ }
+
+ @Override
public void onUserRemoved(@UserIdInt int userId) {
// Clean up any ActivityTaskManager state (by telling it the user is stopped)
mAtmInternal.onUserStopped(userId);
@@ -15807,6 +16043,7 @@
synchronized (mProcLock) {
mDeviceIdleAllowlist = allAppids;
mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+ mAppRestrictionController.setDeviceIdleAllowlist(allAppids, exceptIdleAppids);
}
}
}
@@ -15853,6 +16090,23 @@
}
/**
+ * Returns package name by pid.
+ */
+ @Override
+ @Nullable
+ public String getPackageNameByPid(int pid) {
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord app = mPidsSelfLocked.get(pid);
+
+ if (app != null && app.info != null) {
+ return app.info.packageName;
+ }
+
+ return null;
+ }
+ }
+
+ /**
* Sets if the given pid has an overlay UI or not.
*
* @param pid The pid we are setting overlay UI for.
@@ -16567,8 +16821,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 +17048,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 +17256,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
*/
@@ -17603,6 +17906,11 @@
}
}
+ @Override
+ public boolean isAppFreezerEnabled() {
+ return mOomAdjuster.mCachedAppOptimizer.useFreezer();
+ }
+
/**
* Resets the state of the {@link com.android.server.am.AppErrors} instance.
* This is intended for testing within the CTS only and is protected by
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 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..14d73f6
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -0,0 +1,1200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.
+ */
+ @GuardedBy("mLock")
+ 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 SparseDoubleArray mTmpUidBatteryUsageInWindow = 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.
+ */
+ @GuardedBy("mLock")
+ 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.
+ synchronized (mLock) {
+ scheduleBatteryUsageStatsUpdateIfNecessary(
+ mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now);
+ }
+ }
+ }
+
+ private void checkBatteryUsageStats() {
+ final long now = SystemClock.elapsedRealtime();
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ try {
+ final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow;
+ synchronized (mLock) {
+ copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers);
+ }
+ 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.
+ synchronized (mLock) {
+ 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;
+ synchronized (mLock) {
+ 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.
+ synchronized (mLock) {
+ copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration);
+ }
+ needUpdateUidBatteryUsageInWindow = false;
+ }
+
+ // Add the delta into the global records.
+ synchronized (mLock) {
+ 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);
+ synchronized (mLock) {
+ 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);
+ synchronized (mLock) {
+ 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..465623f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -0,0 +1,2161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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_CARRIER_PRIVILEGED_APP;
+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_ALLOW_LISTED;
+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.telephony.TelephonyManager;
+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.SystemConfig;
+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<>();
+
+ /**
+ * The pre-config packages that are exempted from the background restrictions.
+ */
+ private ArraySet<String> mBgRestrictionExemptioFromSysConfig;
+
+ /**
+ * Lock specifically for bookkeeping around the carrier-privileged app set.
+ * Do not acquire any other locks while holding this one. Methods that
+ * require this lock to be held are named with a "CPL" suffix.
+ */
+ private final Object mCarrierPrivilegedLock = new Object();
+
+ /**
+ * List of carrier-privileged apps that should be excluded from standby.
+ */
+ @GuardedBy("mCarrierPrivilegedLock")
+ private List<String> mCarrierPrivilegedApps;
+
+ 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();
+ initBgRestrictionExemptioFromSysConfig();
+ 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 initBgRestrictionExemptioFromSysConfig() {
+ mBgRestrictionExemptioFromSysConfig =
+ SystemConfig.getInstance().getBgRestrictionExemption();
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
+ for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
+ Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
+ }
+ }
+ }
+
+ private boolean isExemptedFromSysConfig(String packageName) {
+ return mBgRestrictionExemptioFromSysConfig != null
+ && mBgRestrictionExemptioFromSysConfig.contains(packageName);
+ }
+
+ 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, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ 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) {
+ final int appId = UserHandle.getAppId(uid);
+
+ return Arrays.binarySearch(mDeviceIdleAllowlist, appId) >= 0
+ || Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, 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)) {
+ 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;
+ } else if (isCarrierApp(pkg)) {
+ return REASON_CARRIER_PRIVILEGED_APP;
+ } else if (isExemptedFromSysConfig(pkg)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ }
+ }
+ }
+ 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 isCarrierApp(String packageName) {
+ synchronized (mCarrierPrivilegedLock) {
+ if (mCarrierPrivilegedApps == null) {
+ fetchCarrierPrivilegedAppsCPL();
+ }
+ if (mCarrierPrivilegedApps != null) {
+ return mCarrierPrivilegedApps.contains(packageName);
+ }
+ return false;
+ }
+ }
+
+ private void clearCarrierPrivilegedApps() {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Clearing carrier privileged apps list");
+ }
+ synchronized (mCarrierPrivilegedLock) {
+ mCarrierPrivilegedApps = null; // Need to be refetched.
+ }
+ }
+
+ @GuardedBy("mCarrierPrivilegedLock")
+ private void fetchCarrierPrivilegedAppsCPL() {
+ final TelephonyManager telephonyManager = mInjector.getTelephonyManager();
+ mCarrierPrivilegedApps =
+ telephonyManager.getCarrierPrivilegedPackagesForAllActiveSubscriptions();
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+ }
+ }
+
+ 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;
+ private TelephonyManager mTelephonyManager;
+
+ 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;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ if (mTelephonyManager == null) {
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+ }
+ return mTelephonyManager;
+ }
+
+ 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);
+ }
+ }
+ }
+ // fall through.
+ case Intent.ACTION_PACKAGE_CHANGED: {
+ final String pkgName = intent.getData().getSchemeSpecificPart();
+ final String[] cmpList = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ // If this is PACKAGE_ADDED (cmpList == null), or if it's a whole-package
+ // enable/disable event (cmpList is just the package name itself), drop
+ // our carrier privileged app & system-app caches and let them refresh
+ if (cmpList == null
+ || (cmpList.length == 1 && pkgName.equals(cmpList[0]))) {
+ clearCarrierPrivilegedApps();
+ }
+ } 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_CHANGED);
+ 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/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 8561b61..1131fa8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -451,7 +451,7 @@
mUidsToRemove.clear();
mCurrentFuture = null;
mUseLatestStates = true;
- if (updateFlags == UPDATE_ALL) {
+ if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
cancelSyncDueToBatteryLevelChangeLocked();
}
if ((updateFlags & UPDATE_CPU) != 0) {
@@ -496,7 +496,11 @@
Slog.wtf(TAG, "Error updating external stats: ", e);
}
- if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
+ if ((updateFlags & RESET) != 0) {
+ synchronized (BatteryExternalStatsWorker.this) {
+ mLastCollectionTimeStamp = 0;
+ }
+ } else if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
synchronized (BatteryExternalStatsWorker.this) {
mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
}
@@ -658,7 +662,7 @@
mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff, cpuClusterChargeUC);
}
- if (updateFlags == UPDATE_ALL) {
+ if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
mStats.updateKernelWakelocksLocked(elapsedRealtimeUs);
mStats.updateKernelMemoryBandwidthLocked(elapsedRealtimeUs);
}
@@ -731,7 +735,7 @@
uptime, networkStatsManager);
}
- if (updateFlags == UPDATE_ALL) {
+ if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
// This helps mStats deal with ignoring data from prior to resets.
mStats.informThatAllExternalStatsAreFlushed();
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0b92954..2f7249e 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);
@@ -792,12 +797,19 @@
final BatteryUsageStats bus;
switch (atomTag) {
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
- bus = getBatteryUsageStats(List.of(BatteryUsageStatsQuery.DEFAULT)).get(0);
+ final BatteryUsageStatsQuery querySinceReset =
+ new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .build();
+ bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
- final BatteryUsageStatsQuery powerProfileQuery =
- new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build();
- bus = getBatteryUsageStats(List.of(powerProfileQuery)).get(0);
+ final BatteryUsageStatsQuery queryPowerProfile =
+ new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .powerProfileModeledOnly()
+ .build();
+ bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
if (!BATTERY_USAGE_STORE_ENABLED) {
@@ -807,10 +819,12 @@
final long sessionStart = mBatteryUsageStatsStore
.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd = mStats.getStartClockTime();
- final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
- .aggregateSnapshots(sessionStart, sessionEnd)
- .build();
- bus = getBatteryUsageStats(List.of(query)).get(0);
+ final BatteryUsageStatsQuery queryBeforeReset =
+ new BatteryUsageStatsQuery.Builder()
+ .includeProcessStateData()
+ .aggregateSnapshots(sessionStart, sessionEnd)
+ .build();
+ bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
mBatteryUsageStatsStore
.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
@@ -1360,7 +1374,7 @@
}
public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
- final int serviceType) {
+ final int serviceType, final int nrFrequency) {
enforceCallingPermission();
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1368,7 +1382,7 @@
mHandler.post(() -> {
synchronized (mStats) {
mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType,
- elapsedRealtime, uptime);
+ nrFrequency, elapsedRealtime, uptime);
}
});
}
@@ -1957,6 +1971,32 @@
}
}
+ /**
+ * Bluetooth on stat logging
+ */
+ public void noteBluetoothOn(int uid, int reason, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+ Binder.getCallingPid(), uid, null);
+ }
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
+ reason, packageName);
+ }
+
+ /**
+ * Bluetooth off stat logging
+ */
+ public void noteBluetoothOff(int uid, int reason, String packageName) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT,
+ Binder.getCallingPid(), uid, null);
+ }
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+ uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
+ reason, packageName);
+ }
+
@Override
public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ec6b782..a83fdd0 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;
@@ -54,6 +56,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
import android.os.Process;
@@ -320,7 +323,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)).
@@ -328,7 +335,7 @@
mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
// Tell the application to launch this receiver.
- maybeReportBroadcastDispatchedEventLocked(r);
+ maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
r.intent.setComponent(r.curComponent);
boolean started = false;
@@ -864,7 +871,7 @@
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
+ " not specifying RECEIVER_EXPORTED");
- // skip = true;
+ skip = true;
}
if (skip) {
@@ -921,7 +928,7 @@
r.receiverTime = SystemClock.uptimeMillis();
maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
- maybeReportBroadcastDispatchedEventLocked(r);
+ maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
new Intent(r.intent), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId);
@@ -1850,22 +1857,35 @@
return null;
}
- private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) {
+ private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
+ // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set
+ // getIdForResponseEvent.
+ // TODO (217251579): Temporarily use temp-allowlist reason to identify
+ // push messages and record response events.
+ useTemporaryAllowlistReasonAsSignal(r);
+ if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
+ return;
+ }
final String targetPackage = getTargetPackage(r);
// Ignore non-explicit broadcasts
if (targetPackage == null) {
return;
}
- // TODO (206518114): Only allow apps with ACCESS_PACKAGE_USAGE_STATS to set
- // getIdForResponseEvent.
- if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
- return;
- }
- // TODO (206518114): Only report this event when the broadcast is dispatched while the app
- // is in the background state.
getUsageStatsManagerInternal().reportBroadcastDispatched(
r.callingUid, targetPackage, UserHandle.of(r.userId),
- r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime());
+ r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
+ mService.getUidStateLocked(targetUid));
+ }
+
+ private void useTemporaryAllowlistReasonAsSignal(BroadcastRecord r) {
+ if (r.options == null || r.options.getIdForResponseEvent() > 0) {
+ return;
+ }
+ final int reasonCode = r.options.getTemporaryAppAllowlistReasonCode();
+ if (reasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING
+ || reasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA) {
+ r.options.recordResponseEventWhileInBackground(reasonCode);
+ }
}
@NonNull
@@ -2066,6 +2086,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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6f22c61..7af73eb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -977,7 +977,7 @@
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received sync transactions while frozen, killing");
app.killLocked("Sync transaction while in frozen state",
- ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
processKilled = true;
}
@@ -990,7 +990,7 @@
Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
+ app.processName + ". Killing it. Exception: " + e);
app.killLocked("Unable to query binder frozen stats",
- ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
processKilled = true;
}
@@ -1007,7 +1007,7 @@
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
app.killLocked("Unable to unfreeze",
- ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
return;
}
@@ -1423,7 +1423,7 @@
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
- ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
@@ -1477,7 +1477,7 @@
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
- ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index 23553a7..aef7a6c 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -30,6 +30,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.os.Build;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -56,6 +57,8 @@
* "quick & dirty". For example, to define aliases, we use a regular intent filter and meta-data
* in the manifest, instead of adding proper tags/attributes to AndroidManifest.xml.
*
+ * Because it's an experimental feature, it can't be enabled on a user build.
+ *
* Also, for now, aliases can be defined across any packages, but in the final version, there'll
* be restrictions:
* - We probably should only allow either privileged or preinstalled apps.
@@ -78,6 +81,9 @@
private final Context mContext;
@GuardedBy("mLock")
+ private boolean mEnabledByDeviceConfig;
+
+ @GuardedBy("mLock")
private boolean mEnabled;
@GuardedBy("mLock")
@@ -141,7 +147,7 @@
/**
* Call this on systemRead().
*/
- public void onSystemReady(String overrides) {
+ public void onSystemReady(boolean enabledByDeviceConfig, String overrides) {
synchronized (mLock) {
mPlatformCompat = (PlatformCompat) ServiceManager.getService(
Context.PLATFORM_COMPAT_SERVICE);
@@ -149,19 +155,21 @@
mCompatChangeListener);
}
if (DEBUG) Slog.d(TAG, "Compat listener set.");
- update(overrides);
+ update(enabledByDeviceConfig, overrides);
}
/**
* (Re-)loads aliases from <meta-data> and the device config override.
*/
- public void update(String overrides) {
+ public void update(boolean enabledByDeviceConfig, String overrides) {
synchronized (mLock) {
if (mPlatformCompat == null) {
return; // System not ready.
}
- final boolean enabled = mPlatformCompat.isChangeEnabledByPackageName(
- USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM);
+ final boolean enabled = Build.isDebuggable()
+ && (enabledByDeviceConfig
+ || mPlatformCompat.isChangeEnabledByPackageName(
+ USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM));
if (enabled != mEnabled) {
Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases...");
if (enabled) {
@@ -172,6 +180,7 @@
}
}
mEnabled = enabled;
+ mEnabledByDeviceConfig = enabledByDeviceConfig;
mOverrideString = overrides;
if (mEnabled) {
@@ -184,7 +193,7 @@
private void refresh() {
synchronized (mLock) {
- update(mOverrideString);
+ update(mEnabledByDeviceConfig, mOverrideString);
}
}
diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java
index 6e39a4c..f0910dc 100644
--- a/services/core/java/com/android/server/am/DataConnectionStats.java
+++ b/services/core/java/com/android/server/am/DataConnectionStats.java
@@ -109,7 +109,7 @@
}
try {
mBatteryStats.notePhoneDataConnectionState(networkType, visible,
- mServiceState.getState());
+ mServiceState.getState(), mServiceState.getNrFrequencyRange());
} catch (RemoteException e) {
Log.w(TAG, "Error noting data connection state", e);
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 24e7ba4..da78e2d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -570,6 +570,14 @@
ComponentName instanceName, String definingPackageName, int definingUid,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
+ this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
+ restarter, false);
+ }
+
+ ServiceRecord(ActivityManagerService ams, ComponentName name,
+ ComponentName instanceName, String definingPackageName, int definingUid,
+ Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
+ Runnable restarter, boolean isSupplementalProcessService) {
this.ams = ams;
this.name = name;
this.instanceName = instanceName;
@@ -580,7 +588,8 @@
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
packageName = sInfo.applicationInfo.packageName;
- if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
+ if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ || isSupplementalProcessService) {
processName = sInfo.processName + ":" + instanceName.getClassName();
} else {
processName = sInfo.processName;
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..551773e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,11 @@
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
+import static com.android.internal.R.styleable.GameModeConfig_allowGameFpsOverride;
+import static com.android.internal.R.styleable.GameModeConfig_supportsBatteryGameMode;
+import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanceGameMode;
import static com.android.server.wm.CompatModePackages.DOWNSCALED;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_30;
import static com.android.server.wm.CompatModePackages.DOWNSCALE_35;
@@ -54,6 +59,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.net.Uri;
import android.os.Binder;
@@ -71,8 +80,10 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.ArrayMap;
+import android.util.AttributeSet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -83,9 +94,13 @@
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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -128,6 +143,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 +157,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
@@ -148,6 +175,29 @@
new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ writer.println("Permission Denial: can't dump GameManagerService from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+ if (args == null || args.length == 0) {
+ writer.println("*Dump GameManagerService*");
+ dumpAllGameConfigs(writer);
+ }
+ }
+
+ private void dumpAllGameConfigs(PrintWriter pw) {
+ final int userId = ActivityManager.getCurrentUser();
+ String[] packageList = getInstalledGamePackageNames(userId);
+ for (final String packageName : packageList) {
+ pw.println(getInterventionList(packageName));
+ }
+ }
+
class SettingsHandler extends Handler {
SettingsHandler(Looper looper) {
@@ -390,12 +440,20 @@
public static final String METADATA_BATTERY_MODE_ENABLE =
"com.android.app.gamemode.battery.enabled";
+ /**
+ * Metadata that allows a game to specify all intervention information with an XML file in
+ * the application field.
+ */
+ public static final String METADATA_GAME_MODE_CONFIG = "android.game_mode_config";
+
+ private static final String GAME_MODE_CONFIG_NODE_NAME = "game-mode-config";
private final String mPackageName;
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
private boolean mAllowAngle;
+ private boolean mAllowFpsOverride;
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
@@ -403,18 +461,21 @@
try {
final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
PackageManager.GET_META_DATA, userId);
- if (ai.metaData != null) {
- mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
- mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
- mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
- mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
- } else {
- mPerfModeOptedIn = false;
- mBatteryModeOptedIn = false;
- mAllowDownscale = true;
- mAllowAngle = true;
+ if (!parseInterventionFromXml(ai, packageName)) {
+ if (ai.metaData != null) {
+ mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+ mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+ mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+ mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
+ } else {
+ mPerfModeOptedIn = false;
+ mBatteryModeOptedIn = false;
+ mAllowDownscale = true;
+ mAllowAngle = true;
+ mAllowFpsOverride = true;
+ }
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// Not all packages are installed, hence ignore those that are not installed yet.
Slog.v(TAG, "Failed to get package metadata");
}
@@ -434,6 +495,53 @@
}
}
+ private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+ boolean xmlFound = false;
+ try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+ METADATA_GAME_MODE_CONFIG)) {
+ if (parser == null) {
+ Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
+ + " meta-data found for package " + mPackageName);
+ } else {
+ xmlFound = true;
+ final Resources resources = mPackageManager.getResourcesForApplication(
+ packageName);
+ final AttributeSet attributeSet = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Do nothing
+ }
+
+ boolean isStartingTagGameModeConfig =
+ GAME_MODE_CONFIG_NODE_NAME.equals(parser.getName());
+ if (!isStartingTagGameModeConfig) {
+ Slog.w(TAG, "Meta-data does not start with "
+ + GAME_MODE_CONFIG_NODE_NAME
+ + " tag");
+ } else {
+ final TypedArray array = resources.obtainAttributes(attributeSet,
+ com.android.internal.R.styleable.GameModeConfig);
+ mPerfModeOptedIn = array.getBoolean(
+ GameModeConfig_supportsPerformanceGameMode, false);
+ mBatteryModeOptedIn = array.getBoolean(
+ GameModeConfig_supportsBatteryGameMode,
+ false);
+ mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
+ true);
+ mAllowAngle = array.getBoolean(GameModeConfig_allowGameAngleDriver, true);
+ mAllowFpsOverride = array.getBoolean(GameModeConfig_allowGameFpsOverride,
+ true);
+ array.recycle();
+ }
+ }
+ } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
+ Slog.e(TAG, "Error while parsing XML meta-data for "
+ + METADATA_GAME_MODE_CONFIG);
+ }
+ return xmlFound;
+ }
+
/**
* GameModeConfiguration contains all the values for all the interventions associated with
* a game mode.
@@ -462,7 +570,8 @@
mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
- mFps = parser.getString(FPS_KEY, DEFAULT_FPS);
+ mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
+ ? parser.getString(FPS_KEY, DEFAULT_FPS) : DEFAULT_FPS;
// We only want to use ANGLE if:
// - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
// - The app has not opted in to performing the work itself AND
@@ -610,8 +719,6 @@
*/
public static class Lifecycle extends SystemService {
private GameManagerService mService;
- @Nullable
- private GameServiceController mGameServiceController;
public Lifecycle(Context context) {
super(context);
@@ -624,57 +731,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);
}
}
@@ -682,7 +765,7 @@
try {
return mPackageManager.getPackageUidAsUser(packageName, userId)
== Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
return false;
}
}
@@ -846,7 +929,7 @@
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
- public @GameMode boolean getAngleEnabled(String packageName, int userId)
+ public @GameMode boolean isAngleEnabled(String packageName, int userId)
throws SecurityException {
final int gameMode = getGameMode(packageName, userId);
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
@@ -868,14 +951,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 +994,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 +1017,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 +1038,10 @@
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = toUserId;
mHandler.sendMessage(msg);
+
+ if (mGameServiceController != null) {
+ mGameServiceController.notifyNewForegroundUser(to);
+ }
}
/**
@@ -1236,8 +1364,7 @@
.append(packageName);
return listStrSb.toString();
}
- listStrSb.append("\nPackage name: ")
- .append(packageName)
+ listStrSb.append("\n")
.append(packageConfig.toString());
return listStrSb.toString();
}
@@ -1360,7 +1487,7 @@
if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
return;
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (NameNotFoundException e) {
// Ignore the exception.
}
switch (intent.getAction()) {
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..145a298 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -43,6 +43,7 @@
import android.service.games.IGameSessionController;
import android.service.games.IGameSessionService;
import android.util.Slog;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
import com.android.internal.annotations.GuardedBy;
@@ -50,6 +51,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
import com.android.server.wm.WindowManagerService;
import java.util.List;
@@ -62,6 +64,18 @@
private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000;
private static final boolean DEBUG = false;
+ private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener =
+ new TaskSystemBarsListener() {
+ @Override
+ public void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ GameServiceProviderInstanceImpl.this.onTransientSystemBarsVisibilityChanged(
+ taskId, visible, wereRevealedFromSwipeOnSystemBar);
+ }
+ };
+
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
@@ -98,7 +112,10 @@
private final IGameServiceController mGameServiceController =
new IGameServiceController.Stub() {
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public void createGameSession(int taskId) {
+ mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
+ "createGameSession()");
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.createGameSession(taskId);
});
@@ -116,9 +133,10 @@
});
}
- @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY)
public void restartGame(int taskId) {
- mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES,
+ mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY,
"restartGame()");
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.restartGame(taskId);
@@ -199,6 +217,8 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to register task stack listener", e);
}
+
+ mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener);
}
@GuardedBy("mLock")
@@ -214,8 +234,11 @@
Slog.w(TAG, "Failed to unregister task stack listener", e);
}
+ mWindowManagerInternal.unregisterTaskSystemBarsListener(
+ mTaskSystemBarsVisibilityListener);
+
for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
- destroyGameSessionFromRecord(gameSessionRecord);
+ destroyGameSessionFromRecordLocked(gameSessionRecord);
}
mGameSessions.clear();
@@ -303,6 +326,37 @@
}
}
+ private void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ if (visible && !wereRevealedFromSwipeOnSystemBar) {
+ return;
+ }
+
+ GameSessionRecord gameSessionRecord;
+ synchronized (mLock) {
+ gameSessionRecord = mGameSessions.get(taskId);
+ }
+
+ if (gameSessionRecord == null) {
+ return;
+ }
+
+ IGameSession gameSession = gameSessionRecord.getGameSession();
+ if (gameSession == null) {
+ return;
+ }
+
+ try {
+ gameSession.onTransientSystemBarVisibilityFromRevealGestureChanged(visible);
+ } catch (RemoteException ex) {
+ Slog.w(TAG,
+ "Failed to send transient system bars visibility from reveal gesture for task: "
+ + taskId);
+ }
+ }
+
private void createGameSession(int taskId) {
synchronized (mLock) {
createGameSessionLocked(taskId);
@@ -330,7 +384,6 @@
+ ") is not awaiting game session request. Ignoring.");
return;
}
- mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested());
GameSessionViewHostConfiguration gameSessionViewHostConfiguration =
createViewHostConfigurationForTask(taskId);
@@ -373,12 +426,12 @@
}, mBackgroundExecutor);
AndroidFuture<Void> unusedPostCreateGameSessionFuture =
- mGameSessionServiceConnector.post(gameService -> {
+ mGameSessionServiceConnector.post(gameSessionService -> {
CreateGameSessionRequest createGameSessionRequest =
new CreateGameSessionRequest(
taskId,
existingGameSessionRecord.getComponentName().getPackageName());
- gameService.create(
+ gameSessionService.create(
mGameSessionController,
createGameSessionRequest,
gameSessionViewHostConfiguration,
@@ -458,10 +511,11 @@
}
return;
}
- destroyGameSessionFromRecord(gameSessionRecord);
+ destroyGameSessionFromRecordLocked(gameSessionRecord);
}
- private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) {
+ @GuardedBy("mLock")
+ private void destroyGameSessionFromRecordLocked(@NonNull GameSessionRecord gameSessionRecord) {
SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage();
if (surfacePackage != null) {
try {
@@ -534,17 +588,29 @@
@VisibleForTesting
void takeScreenshot(int taskId, @NonNull AndroidFuture callback) {
+ GameSessionRecord gameSessionRecord;
synchronized (mLock) {
- boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
- if (!isTaskAssociatedWithGameSession) {
+ gameSessionRecord = mGameSessions.get(taskId);
+ if (gameSessionRecord == null) {
Slog.w(TAG, "No game session found for id: " + taskId);
callback.complete(GameScreenshotResult.createInternalErrorResult());
return;
}
}
+ final SurfacePackage overlaySurfacePackage = gameSessionRecord.getSurfacePackage();
+ final SurfaceControl overlaySurfaceControl =
+ overlaySurfacePackage != null ? overlaySurfacePackage.getSurfaceControl() : null;
mBackgroundExecutor.execute(() -> {
- final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId);
+ final SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder =
+ new SurfaceControl.LayerCaptureArgs.Builder(/* layer */ null);
+ if (overlaySurfaceControl != null) {
+ SurfaceControl[] excludeLayers = new SurfaceControl[1];
+ excludeLayers[0] = overlaySurfaceControl;
+ layerCaptureArgsBuilder.setExcludeLayers(excludeLayers);
+ }
+ final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId,
+ layerCaptureArgsBuilder);
if (bitmap == null) {
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
callback.complete(GameScreenshotResult.createInternalErrorResult());
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/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 9d4d1c1..366718c 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
import android.apphibernation.IAppHibernationService;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -221,7 +222,7 @@
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
- "Caller does not have MANAGE_APP_HIBERNATION permission.");
+ "Caller did not have permission while calling " + methodName);
userId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
if (!checkUserStatesExist(userId, methodName)) {
@@ -380,6 +381,46 @@
}
/**
+ * Return the stats from app hibernation for each package provided.
+ *
+ * @param packageNames the set of packages to return stats for. Returns all if null
+ * @return map from package to stats for that package
+ */
+ public Map<String, HibernationStats> getHibernationStatsForUser(
+ @Nullable Set<String> packageNames, int userId) {
+ Map<String, HibernationStats> statsMap = new ArrayMap<>();
+ String methodName = "getHibernationStatsForUser";
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_APP_HIBERNATION,
+ "Caller does not have MANAGE_APP_HIBERNATION permission.");
+ userId = handleIncomingUser(userId, methodName);
+ synchronized (mLock) {
+ if (!checkUserStatesExist(userId, methodName)) {
+ return statsMap;
+ }
+ final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId);
+ Set<String> pkgs = packageNames != null ? packageNames : userPackageStates.keySet();
+ for (String pkgName : pkgs) {
+ if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) {
+ // Package not visible to caller
+ continue;
+ }
+ if (!mGlobalHibernationStates.containsKey(pkgName)
+ || !userPackageStates.containsKey(pkgName)) {
+ Slog.w(TAG, String.format(
+ "No hibernation state associated with package %s user %d. Maybe"
+ + "the package was uninstalled? ", pkgName, userId));
+ continue;
+ }
+ HibernationStats stats = new HibernationStats(
+ mGlobalHibernationStates.get(pkgName).savedByte);
+ statsMap.put(pkgName, stats);
+ }
+ }
+ return statsMap;
+ }
+
+ /**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
* not hold {@link #mLock} while calling this to avoid deadlock scenarios.
*/
@@ -788,6 +829,13 @@
}
@Override
+ public Map<String, HibernationStats> getHibernationStatsForUser(
+ @Nullable List<String> packageNames, int userId) {
+ Set<String> pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null;
+ return mService.getHibernationStatsForUser(pkgsSet, userId);
+ }
+
+ @Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err, @NonNull String[] args,
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index a139589..d2fa386 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -22,6 +22,7 @@
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED;
import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
import android.Manifest;
import android.annotation.NonNull;
@@ -29,6 +30,7 @@
import android.app.ActivityThread;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -57,6 +59,7 @@
import android.service.attention.AttentionService.AttentionSuccessCodes;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
import android.text.TextUtils;
import android.util.Slog;
@@ -134,6 +137,15 @@
@GuardedBy("mLock")
AttentionCheck mCurrentAttentionCheck;
+ /**
+ * A proxy for relaying proximity information between the Attention Service and the client.
+ * The proxy will be initialized when the client calls onStartProximityUpdates and will be
+ * disabled only when the client calls onStopProximityUpdates.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ ProximityUpdate mCurrentProximityUpdate;
+
public AttentionManagerService(Context context) {
this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE),
new Object(), null);
@@ -315,6 +327,77 @@
}
}
+ /**
+ * Requests the continuous updates of proximity signal via the provided callback,
+ * until the given callback is stopped.
+ *
+ * Calling this multiple times for duplicate requests will be no-ops, returning true.
+ *
+ * @return {@code true} if the framework was able to dispatch the request
+ */
+ @VisibleForTesting
+ boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ Objects.requireNonNull(callbackInternal);
+ if (!mIsServiceEnabled) {
+ Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device.");
+ return false;
+ }
+
+ if (!isServiceAvailable()) {
+ Slog.w(LOG_TAG, "Service is not available at this moment.");
+ return false;
+ }
+
+ // don't allow proximity request in screen off state.
+ // This behavior might change in the future.
+ if (!mPowerManager.isInteractive()) {
+ Slog.w(LOG_TAG, "Proximity Service is unavailable during screen off at this moment.");
+ return false;
+ }
+
+ synchronized (mLock) {
+ // schedule shutting down the connection if no one resets this timer
+ freeIfInactiveLocked();
+
+ // lazily start the service, which should be very lightweight to start
+ bindLocked();
+
+ /*
+ Prevent spamming with multiple requests, only one at a time is allowed.
+ If there are use-cases for keeping track of multiple requests, we
+ can refactor ProximityUpdate object to keep track of multiple internal callbacks.
+ */
+ if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+ if (mCurrentProximityUpdate.mCallbackInternal == callbackInternal) {
+ Slog.w(LOG_TAG, "Provided callback is already registered. Skipping.");
+ return true;
+ } else {
+ // reject the new request since the old request is still alive.
+ Slog.w(LOG_TAG, "New proximity update cannot be processed because there is "
+ + "already an ongoing update");
+ return false;
+ }
+ }
+ mCurrentProximityUpdate = new ProximityUpdate(callbackInternal);
+ return mCurrentProximityUpdate.startUpdates();
+ }
+ }
+
+ /** Cancels the specified proximity registration. */
+ @VisibleForTesting
+ void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ synchronized (mLock) {
+ if (mCurrentProximityUpdate == null
+ || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal)
+ || !mCurrentProximityUpdate.mStartedUpdates) {
+ Slog.w(LOG_TAG, "Cannot stop a non-current callback");
+ return;
+ }
+ mCurrentProximityUpdate.cancelUpdates();
+ mCurrentProximityUpdate = null;
+ }
+ }
+
@GuardedBy("mLock")
@VisibleForTesting
protected void freeIfInactiveLocked() {
@@ -390,15 +473,18 @@
ipw.println("Class=" + mComponentName.getClassName());
ipw.decreaseIndent();
}
- ipw.println("binding=" + mBinding);
- ipw.println("current attention check:");
synchronized (mLock) {
+ ipw.println("binding=" + mBinding);
+ ipw.println("current attention check:");
if (mCurrentAttentionCheck != null) {
mCurrentAttentionCheck.dump(ipw);
}
if (mAttentionCheckCacheBuffer != null) {
mAttentionCheckCacheBuffer.dump(ipw);
}
+ if (mCurrentProximityUpdate != null) {
+ mCurrentProximityUpdate.dump(ipw);
+ }
}
}
@@ -417,6 +503,17 @@
public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
AttentionManagerService.this.cancelAttentionCheck(callbackInternal);
}
+
+ @Override
+ public boolean onStartProximityUpdates(
+ ProximityCallbackInternal callback) {
+ return AttentionManagerService.this.onStartProximityUpdates(callback);
+ }
+
+ @Override
+ public void onStopProximityUpdates(ProximityCallbackInternal callback) {
+ AttentionManagerService.this.onStopProximityUpdates(callback);
+ }
}
@VisibleForTesting
@@ -536,6 +633,71 @@
}
}
+ @VisibleForTesting
+ final class ProximityUpdate {
+ private final ProximityCallbackInternal mCallbackInternal;
+ private final IProximityCallback mIProximityCallback;
+ private boolean mStartedUpdates;
+
+ ProximityUpdate(ProximityCallbackInternal callbackInternal) {
+ mCallbackInternal = callbackInternal;
+ mIProximityCallback = new IProximityCallback.Stub() {
+ @Override
+ public void onProximityUpdate(double distance) {
+ synchronized (mLock) {
+ mCallbackInternal.onProximityUpdate(distance);
+ freeIfInactiveLocked();
+ }
+ }
+ };
+ }
+
+ boolean startUpdates() {
+ synchronized (mLock) {
+ if (mStartedUpdates) {
+ Slog.w(LOG_TAG, "Already registered to a proximity service.");
+ return false;
+ }
+ if (mService == null) {
+ Slog.w(LOG_TAG,
+ "There is no service bound. Proximity update request rejected.");
+ return false;
+ }
+ try {
+ mService.onStartProximityUpdates(mIProximityCallback);
+ mStartedUpdates = true;
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void cancelUpdates() {
+ synchronized (mLock) {
+ if (mStartedUpdates) {
+ if (mService == null) {
+ mStartedUpdates = false;
+ return;
+ }
+ try {
+ mService.onStopProximityUpdates();
+ mStartedUpdates = false;
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ }
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ ipw.increaseIndent();
+ ipw.println("is StartedUpdates=" + mStartedUpdates);
+ ipw.decreaseIndent();
+ }
+ }
+
private void appendResultToAttentionCacheBuffer(AttentionCheckCache cache) {
synchronized (mLock) {
if (mAttentionCheckCacheBuffer == null) {
@@ -593,6 +755,18 @@
mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
}
}
+ if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
+ if (mService != null) {
+ try {
+ mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
+ }
+ } else {
+ mCurrentProximityUpdate.cancelUpdates();
+ mCurrentProximityUpdate = null;
+ }
+ }
}
@VisibleForTesting
@@ -609,7 +783,9 @@
switch (msg.what) {
// Do not occupy resources when not in use - unbind proactively.
case CHECK_CONNECTION_EXPIRATION: {
- cancelAndUnbindLocked();
+ synchronized (mLock) {
+ cancelAndUnbindLocked();
+ }
}
break;
@@ -653,10 +829,15 @@
@GuardedBy("mLock")
private void cancelAndUnbindLocked() {
synchronized (mLock) {
- if (mCurrentAttentionCheck == null) {
+ if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
return;
}
- cancel();
+ if (mCurrentAttentionCheck != null) {
+ cancel();
+ }
+ if (mCurrentProximityUpdate != null) {
+ mCurrentProximityUpdate.cancelUpdates();
+ }
if (mService == null) {
return;
}
@@ -702,7 +883,9 @@
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
- cancelAndUnbindLocked();
+ synchronized (mLock) {
+ cancelAndUnbindLocked();
+ }
}
}
}
@@ -730,8 +913,27 @@
}
}
+ class TestableProximityCallbackInternal extends ProximityCallbackInternal {
+ private double mLastCallbackCode = PROXIMITY_UNKNOWN;
+
+ @Override
+ public void onProximityUpdate(double distance) {
+ mLastCallbackCode = distance;
+ }
+
+ public void reset() {
+ mLastCallbackCode = PROXIMITY_UNKNOWN;
+ }
+
+ public double getLastCallbackCode() {
+ return mLastCallbackCode;
+ }
+ }
+
final TestableAttentionCallbackInternal mTestableAttentionCallback =
new TestableAttentionCallbackInternal();
+ final TestableProximityCallbackInternal mTestableProximityCallback =
+ new TestableProximityCallbackInternal();
@Override
public int onCommand(@Nullable final String cmd) {
@@ -749,6 +951,10 @@
return cmdCallCheckAttention();
case "cancelCheckAttention":
return cmdCallCancelAttention();
+ case "onStartProximityUpdates":
+ return cmdCallOnStartProximityUpdates();
+ case "onStopProximityUpdates":
+ return cmdCallOnStopProximityUpdates();
default:
throw new IllegalArgumentException("Invalid argument");
}
@@ -758,6 +964,8 @@
return cmdClearTestableAttentionService();
case "getLastTestCallbackCode":
return cmdGetLastTestCallbackCode();
+ case "getLastTestProximityCallbackCode":
+ return cmdGetLastTestProximityCallbackCode();
default:
return handleDefaultCommands(cmd);
}
@@ -782,6 +990,7 @@
private int cmdClearTestableAttentionService() {
sTestAttentionServicePackage = "";
mTestableAttentionCallback.reset();
+ mTestableProximityCallback.reset();
resetStates();
return 0;
}
@@ -800,6 +1009,20 @@
return 0;
}
+ private int cmdCallOnStartProximityUpdates() {
+ final PrintWriter out = getOutPrintWriter();
+ boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback);
+ out.println(calledSuccessfully ? "true" : "false");
+ return 0;
+ }
+
+ private int cmdCallOnStopProximityUpdates() {
+ final PrintWriter out = getOutPrintWriter();
+ onStopProximityUpdates(mTestableProximityCallback);
+ out.println("true");
+ return 0;
+ }
+
private int cmdResolveAttentionServiceComponent() {
final PrintWriter out = getOutPrintWriter();
ComponentName resolvedComponent = resolveAttentionService(mContext);
@@ -813,7 +1036,16 @@
return 0;
}
+ private int cmdGetLastTestProximityCallbackCode() {
+ final PrintWriter out = getOutPrintWriter();
+ out.println(mTestableProximityCallback.getLastCallbackCode());
+ return 0;
+ }
+
private void resetStates() {
+ synchronized (mLock) {
+ mCurrentProximityUpdate = null;
+ }
mComponentName = resolveAttentionService(mContext);
}
@@ -844,11 +1076,24 @@
+ " (to see the result, call getLastTestCallbackCode)");
out.println(" := false, otherwise");
out.println(" call cancelCheckAttention: Cancels check attention");
+ out.println(" call onStartProximityUpdates: Calls onStartProximityUpdates");
+ out.println(" ---returns:");
+ out.println(
+ " := true, if the request was successfully dispatched to the service "
+ + "implementation."
+ + " (to see the result, call getLastTestProximityCallbackCode)");
+ out.println(" := false, otherwise");
+ out.println(" call onStopProximityUpdates: Cancels proximity updates");
out.println(" getLastTestCallbackCode");
out.println(" ---returns:");
out.println(
" := An integer, representing the last callback code received from the "
+ "bounded implementation. If none, it will return -1");
+ out.println(" getLastTestProximityCallbackCode");
+ out.println(" ---returns:");
+ out.println(
+ " := A double, representing the last proximity value received from the "
+ + "bounded implementation. If none, it will return -1.0");
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 65be5f0..daf3561 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -29,7 +29,7 @@
import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import android.media.IAudioRoutesObserver;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
@@ -500,12 +500,11 @@
}
}
- /*package*/ void setWiredDeviceConnectionState(int type,
- @AudioService.ConnectionState int state, String address, String name,
- String caller) {
+ /*package*/ void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+ @AudioService.ConnectionState int state, String caller) {
//TODO move logging here just like in setBluetooth* methods
synchronized (mDeviceStateLock) {
- mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+ mDeviceInventory.setWiredDeviceConnectionState(attributes, state, caller);
}
}
@@ -531,12 +530,12 @@
/*package*/ static final class BtDeviceChangedData {
final @Nullable BluetoothDevice mNewDevice;
final @Nullable BluetoothDevice mPreviousDevice;
- final @NonNull BtProfileConnectionInfo mInfo;
+ final @NonNull BluetoothProfileConnectionInfo mInfo;
final @NonNull String mEventSource;
BtDeviceChangedData(@Nullable BluetoothDevice newDevice,
@Nullable BluetoothDevice previousDevice,
- @NonNull BtProfileConnectionInfo info, @NonNull String eventSource) {
+ @NonNull BluetoothProfileConnectionInfo info, @NonNull String eventSource) {
mNewDevice = newDevice;
mPreviousDevice = previousDevice;
mInfo = info;
@@ -568,9 +567,9 @@
mDevice = device;
mState = state;
mProfile = d.mInfo.getProfile();
- mSupprNoisy = d.mInfo.getSuppressNoisyIntent();
+ mSupprNoisy = d.mInfo.isSuppressNoisyIntent();
mVolume = d.mInfo.getVolume();
- mIsLeOutput = d.mInfo.getIsLeOutput();
+ mIsLeOutput = d.mInfo.isLeOutput();
mEventSource = d.mEventSource;
mAudioSystemDevice = audioDevice;
mMusicDevice = AudioSystem.DEVICE_NONE;
@@ -641,7 +640,7 @@
audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID;
break;
case BluetoothProfile.LE_AUDIO:
- if (d.mInfo.getIsLeOutput()) {
+ if (d.mInfo.isLeOutput()) {
audioDevice = AudioSystem.DEVICE_OUT_BLE_HEADSET;
} else {
audioDevice = AudioSystem.DEVICE_IN_BLE_HEADSET;
@@ -1013,11 +1012,9 @@
}
}
- /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName) {
+ /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName,
- false /*for test*/);
+ return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 8a62c34..0e29041 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -230,19 +230,15 @@
* A class just for packaging up a set of connection parameters.
*/
/*package*/ class WiredDeviceConnectionState {
- public final int mType;
+ public final AudioDeviceAttributes mAttributes;
public final @AudioService.ConnectionState int mState;
- public final String mAddress;
- public final String mName;
public final String mCaller;
public boolean mForTest = false;
- /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
- String address, String name, String caller) {
- mType = type;
+ /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes,
+ @AudioService.ConnectionState int state, String caller) {
+ mAttributes = attributes;
mState = state;
- mAddress = address;
- mName = name;
mCaller = caller;
}
}
@@ -280,11 +276,10 @@
synchronized (mDevicesLock) {
//TODO iterate on mApmConnectedDevices instead once it handles all device types
for (DeviceInfo di : mConnectedDevices.values()) {
- mAudioSystem.setDeviceConnectionState(
- di.mDeviceType,
- AudioSystem.DEVICE_STATE_AVAILABLE,
+ mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType,
di.mDeviceAddress,
- di.mDeviceName,
+ di.mDeviceName),
+ AudioSystem.DEVICE_STATE_AVAILABLE,
di.mDeviceCodecFormat);
}
}
@@ -519,41 +514,45 @@
/*package*/ void onSetWiredDeviceConnectionState(
AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
+ int type = wdcs.mAttributes.getInternalType();
+
AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onSetWiredDeviceConnectionState")
- .set(MediaMetrics.Property.ADDRESS, wdcs.mAddress)
- .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(wdcs.mType))
+ .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress())
+ .set(MediaMetrics.Property.DEVICE,
+ AudioSystem.getDeviceName(type))
.set(MediaMetrics.Property.STATE,
wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
synchronized (mDevicesLock) {
if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
- && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
+ && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/,
"onSetWiredDeviceConnectionState state DISCONNECTED");
}
- if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
- wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) {
+ if (!handleDeviceConnection(wdcs.mAttributes,
+ wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
return;
}
if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
- if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
+ if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/,
"onSetWiredDeviceConnectionState state not DISCONNECTED");
}
- mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
+ mDeviceBroker.checkMusicActive(type, wdcs.mCaller);
}
- if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
+ if (type == AudioSystem.DEVICE_OUT_HDMI) {
mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
}
- sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
- updateAudioRoutes(wdcs.mType, wdcs.mState);
+ sendDeviceConnectionIntent(type, wdcs.mState,
+ wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
+ updateAudioRoutes(type, wdcs.mState);
}
mmi.record();
}
@@ -572,12 +571,12 @@
return;
}
// Toggle HDMI to retrigger broadcast with proper formats.
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
- "android"); // disconnect
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
- "android"); // reconnect
+ setWiredDeviceConnectionState(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect
+ setWiredDeviceConnectionState(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
+ AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect
}
mmi.record();
}
@@ -707,16 +706,17 @@
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
+ * @param attributes the attributes of the device
* @param connect true if connection
- * @param device the device type
- * @param address the address of the device
- * @param deviceName human-readable name of device
* @param isForTesting if true, not calling AudioSystem for the connection as this is
* just for testing
* @return false if an error was reported by AudioSystem
*/
- /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName, boolean isForTesting) {
+ /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
+ boolean isForTesting) {
+ int device = attributes.getInternalType();
+ String address = attributes.getAddress();
+ String deviceName = attributes.getName();
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
@@ -743,9 +743,8 @@
if (isForTesting) {
res = AudioSystem.AUDIO_STATUS_OK;
} else {
- res = mAudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ res = mAudioSystem.setDeviceConnectionState(attributes,
+ AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
}
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
@@ -762,9 +761,8 @@
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
return true;
} else if (!connect && isConnected) {
- mAudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mAudioSystem.setDeviceConnectionState(attributes,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -941,13 +939,13 @@
return delay;
}
- /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
- String address, String name, String caller) {
+ /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+ @AudioService.ConnectionState int state, String caller) {
synchronized (mDevicesLock) {
- int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
+ int delay = checkSendBecomingNoisyIntentInt(
+ attributes.getInternalType(), state, AudioSystem.DEVICE_NONE);
mDeviceBroker.postSetWiredDeviceConnectionState(
- new WiredDeviceConnectionState(type, state, address, name, caller),
- delay);
+ new WiredDeviceConnectionState(attributes, state, caller), delay);
return delay;
}
}
@@ -955,8 +953,7 @@
/*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
@AudioService.ConnectionState int state) {
final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
- device.getInternalType(), state, device.getAddress(),
- "test device", "com.android.server.audio");
+ device, state, "com.android.server.audio");
connection.mForTest = true;
onSetWiredDeviceConnectionState(connection);
}
@@ -972,8 +969,9 @@
mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
// at this point there could be another A2DP device already connected in APM, but it
// doesn't matter as this new one will overwrite the previous one
- final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+ final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
+ AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
@@ -1035,8 +1033,9 @@
// device to remove was visible by APM, update APM
mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
- final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+ final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
@@ -1074,8 +1073,9 @@
@GuardedBy("mDevicesLock")
private void makeA2dpSrcAvailable(String address) {
- mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
+ mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
@@ -1085,8 +1085,9 @@
@GuardedBy("mDevicesLock")
private void makeA2dpSrcUnavailable(String address) {
- mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
@@ -1099,8 +1100,9 @@
AudioSystem.DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
- mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
+ mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+ AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
@@ -1122,8 +1124,9 @@
@GuardedBy("mDevicesLock")
private void makeHearingAidDeviceUnavailable(String address) {
- mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_HEARING_AID, address),
+ AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
@@ -1140,14 +1143,14 @@
private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
String eventSource) {
if (device != AudioSystem.DEVICE_NONE) {
-
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
*/
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
- AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE,
- address, name, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name),
+ AudioSystem.DEVICE_STATE_AVAILABLE,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
@@ -1168,8 +1171,9 @@
@GuardedBy("mDevicesLock")
private void makeLeAudioDeviceUnavailable(String address, int device) {
if (device != AudioSystem.DEVICE_NONE) {
- AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE,
- address, "", AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address),
+ AudioSystem.DEVICE_STATE_UNAVAILABLE,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
@@ -1207,7 +1211,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 +1379,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..05955c3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -85,7 +85,7 @@
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
import android.media.IAudioRoutesObserver;
@@ -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
@@ -6372,23 +6371,23 @@
/**
* see AudioManager.setWiredDeviceConnectionState()
*/
- public void setWiredDeviceConnectionState(int type,
- @ConnectionState int state, String address, String name,
- String caller) {
+ public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+ @ConnectionState int state, String caller) {
enforceModifyAudioRoutingPermission();
if (state != CONNECTION_STATE_CONNECTED
&& state != CONNECTION_STATE_DISCONNECTED) {
throw new IllegalArgumentException("Invalid state " + state);
}
new MediaMetrics.Item(mMetricsId + "setWiredDeviceConnectionState")
- .set(MediaMetrics.Property.ADDRESS, address)
+ .set(MediaMetrics.Property.ADDRESS, attributes.getAddress())
.set(MediaMetrics.Property.CLIENT_NAME, caller)
- .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(type))
- .set(MediaMetrics.Property.NAME, name)
+ .set(MediaMetrics.Property.DEVICE,
+ AudioSystem.getDeviceName(attributes.getInternalType()))
+ .set(MediaMetrics.Property.NAME, attributes.getName())
.set(MediaMetrics.Property.STATE,
state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected")
.record();
- mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
+ mDeviceBroker.setWiredDeviceConnectionState(attributes, state, caller);
}
/** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
@@ -6435,14 +6434,14 @@
* See AudioManager.handleBluetoothActiveDeviceChanged(...)
*/
public void handleBluetoothActiveDeviceChanged(BluetoothDevice newDevice,
- BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) {
+ BluetoothDevice previousDevice, @NonNull BluetoothProfileConnectionInfo info) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_STACK)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Bluetooth is the only caller allowed");
}
if (info == null) {
- throw new IllegalArgumentException("Illegal null BtProfileConnectionInfo for device "
- + previousDevice + " -> " + newDevice);
+ throw new IllegalArgumentException("Illegal null BluetoothProfileConnectionInfo for"
+ + " device " + previousDevice + " -> " + newDevice);
}
final int profile = info.getProfile();
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3137fa5..3225274 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -116,10 +116,11 @@
@Override
public String eventToString() {
return new StringBuilder("setWiredDeviceConnectionState(")
- .append(" type:").append(Integer.toHexString(mState.mType))
+ .append(" type:").append(
+ Integer.toHexString(mState.mAttributes.getInternalType()))
.append(" state:").append(AudioSystem.deviceStateToString(mState.mState))
- .append(" addr:").append(mState.mAddress)
- .append(" name:").append(mState.mName)
+ .append(" addr:").append(mState.mAttributes.getAddress())
+ .append(" name:").append(mState.mAttributes.getName())
.append(") from ").append(mState.mCaller).toString();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index a2ba76b..f572261 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -258,19 +258,16 @@
}
/**
- * Same as {@link AudioSystem#setDeviceConnectionState(int, int, String, String, int)}
- * @param device
+ * Same as {@link AudioSystem#setDeviceConnectionState(AudioDeviceAttributes, int, int)}
+ * @param attributes
* @param state
- * @param deviceAddress
- * @param deviceName
* @param codecFormat
* @return
*/
- public int setDeviceConnectionState(int device, int state, String deviceAddress,
- String deviceName, int codecFormat) {
+ public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
+ int codecFormat) {
invalidateRoutingCache();
- return AudioSystem.setDeviceConnectionState(device, state, deviceAddress, deviceName,
- codecFormat);
+ return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat);
}
/**
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 42fca9b..3491cd5 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -31,7 +31,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import android.os.Binder;
import android.os.UserHandle;
import android.provider.Settings;
@@ -40,6 +40,7 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -486,16 +487,16 @@
return;
}
final BluetoothDevice btDevice = deviceList.get(0);
- final @BluetoothProfile.BtProfileState int state =
- proxy.getConnectionState(btDevice);
- if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) {
mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
- new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
+ new BluetoothProfileConnectionInfo(profile),
+ "mBluetoothProfileServiceListener"));
} else {
mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(null, btDevice,
- new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
+ new BluetoothProfileConnectionInfo(profile),
+ "mBluetoothProfileServiceListener"));
}
}
@@ -505,7 +506,12 @@
// Discard timeout message
mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
mBluetoothHeadset = headset;
- setBtScoActiveDevice(headset != null ? headset.getActiveDevice() : null);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ List<BluetoothDevice> activeDevices = Collections.emptyList();
+ if (adapter != null) {
+ activeDevices = adapter.getActiveDevices(BluetoothProfile.HEADSET);
+ }
+ setBtScoActiveDevice((activeDevices.size() > 0) ? activeDevices.get(0) : null);
// Refresh SCO audio state
checkScoAudioState();
if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
@@ -589,8 +595,9 @@
String btDeviceName = getName(btDevice);
boolean result = false;
if (isActive) {
- result |= mDeviceBroker.handleDeviceConnection(isActive, audioDevice.getInternalType(),
- audioDevice.getAddress(), btDeviceName);
+ result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
+ audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
+ isActive);
} else {
int[] outDeviceTypes = {
AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -598,13 +605,15 @@
AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
};
for (int outDeviceType : outDeviceTypes) {
- result |= mDeviceBroker.handleDeviceConnection(
- isActive, outDeviceType, audioDevice.getAddress(), btDeviceName);
+ result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
+ outDeviceType, audioDevice.getAddress(), btDeviceName),
+ isActive);
}
}
// handleDeviceConnection() && result to make sure the method get executed
- result = mDeviceBroker.handleDeviceConnection(
- isActive, inDevice, audioDevice.getAddress(), btDeviceName) && result;
+ result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
+ inDevice, audioDevice.getAddress(), btDeviceName),
+ isActive) && result;
return result;
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 0da6a1b..79705a3 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -53,6 +53,7 @@
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -64,6 +65,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -681,16 +683,18 @@
+ ", Latency: " + latency);
}
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
+ final OperationContext operationContext = new OperationContext();
+ operationContext.isCrypto = isCrypto();
+ BiometricFrameworkStatsLogger.getInstance().authenticate(
+ operationContext,
statsModality(),
- mUserId,
- isCrypto(),
+ BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- mPreAuthInfo.confirmationRequested,
- FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
- latency,
mDebugEnabled,
- -1 /* sensorId */,
+ latency,
+ FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+ mPreAuthInfo.confirmationRequested,
+ mUserId,
-1f /* ambientLightLux */);
} else {
final long latency = System.currentTimeMillis() - mStartTimeMs;
@@ -711,17 +715,18 @@
+ ", Latency: " + latency);
}
// Auth canceled
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
+ final OperationContext operationContext = new OperationContext();
+ operationContext.isCrypto = isCrypto();
+ BiometricFrameworkStatsLogger.getInstance().error(
+ operationContext,
statsModality(),
- mUserId,
- isCrypto(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- error,
- 0 /* vendorCode */,
mDebugEnabled,
latency,
- -1 /* sensorId */);
+ error,
+ 0 /* vendorCode */,
+ mUserId);
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
new file mode 100644
index 0000000..c5e266f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -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.server.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.common.OperationContext;
+
+import java.util.function.Consumer;
+
+/**
+ * Cache for system state not directly related to biometric operations that is used for
+ * logging or optimizations.
+ */
+public interface BiometricContext {
+ /** Gets the context source from the system context. */
+ static BiometricContext getInstance(@NonNull Context context) {
+ return BiometricContextProvider.defaultProvider(context);
+ }
+
+ /** Update the given context with the most recent values and return it. */
+ OperationContext updateContext(@NonNull OperationContext operationContext,
+ boolean isCryptoOperation);
+
+ /** The session id for keyguard entry, if active, or null. */
+ @Nullable Integer getKeyguardEntrySessionId();
+
+ /** The session id for biometric prompt usage, if active, or null. */
+ @Nullable Integer getBiometricPromptSessionId();
+
+ /** If the display is in AOD. */
+ boolean isAoD();
+
+ /**
+ * Subscribe to context changes.
+ *
+ * @param context context that will be modified when changed
+ * @param consumer callback when the context is modified
+ */
+ void subscribe(@NonNull OperationContext context, @NonNull Consumer<OperationContext> consumer);
+
+ /** Unsubscribe from context changes. */
+ void unsubscribe(@NonNull OperationContext context);
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
new file mode 100644
index 0000000..70acaff
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.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.biometrics.log;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A default provider for {@link BiometricContext}.
+ */
+class BiometricContextProvider implements BiometricContext {
+
+ private static final String TAG = "BiometricContextProvider";
+
+ private static final int SESSION_TYPES =
+ StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT;
+
+ private static BiometricContextProvider sInstance;
+
+ static BiometricContextProvider defaultProvider(@NonNull Context context) {
+ synchronized (BiometricContextProvider.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new BiometricContextProvider(
+ new AmbientDisplayConfiguration(context),
+ IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
+ Context.STATUS_BAR_SERVICE)), null /* handler */);
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException("Failed to find required service", e);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ @NonNull
+ private final Map<OperationContext, Consumer<OperationContext>> mSubscribers =
+ new ConcurrentHashMap<>();
+
+ @Nullable
+ private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
+
+ private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ private boolean mIsDozing = false;
+
+ @VisibleForTesting
+ BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+ @NonNull IStatusBarService service, @Nullable Handler handler) {
+ mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ try {
+ service.setBiometicContextListener(new IBiometricContextListener.Stub() {
+ @Override
+ public void onDozeChanged(boolean isDozing) {
+ mIsDozing = isDozing;
+ notifyChanged();
+ }
+
+ private void notifyChanged() {
+ if (handler != null) {
+ handler.post(() -> notifySubscribers());
+ } else {
+ notifySubscribers();
+ }
+ }
+ });
+ service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
+ @Override
+ public void onSessionStarted(int sessionType, InstanceId instance) {
+ mSession.put(sessionType, instance);
+ }
+
+ @Override
+ public void onSessionEnded(int sessionType, InstanceId instance) {
+ final InstanceId id = mSession.remove(sessionType);
+ if (id != null && instance != null && id.getId() != instance.getId()) {
+ Slog.w(TAG, "session id mismatch");
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register biometric context listener", e);
+ }
+ }
+
+ @Override
+ public OperationContext updateContext(@NonNull OperationContext operationContext,
+ boolean isCryptoOperation) {
+ operationContext.isAoD = isAoD();
+ operationContext.isCrypto = isCryptoOperation;
+ setFirstSessionId(operationContext);
+ return operationContext;
+ }
+
+ private void setFirstSessionId(@NonNull OperationContext operationContext) {
+ Integer sessionId = getKeyguardEntrySessionId();
+ if (sessionId != null) {
+ operationContext.id = sessionId;
+ operationContext.reason = OperationReason.KEYGUARD;
+ return;
+ }
+
+ sessionId = getBiometricPromptSessionId();
+ if (sessionId != null) {
+ operationContext.id = sessionId;
+ operationContext.reason = OperationReason.BIOMETRIC_PROMPT;
+ return;
+ }
+
+ operationContext.id = 0;
+ operationContext.reason = OperationReason.UNKNOWN;
+ }
+
+ @Nullable
+ @Override
+ public Integer getKeyguardEntrySessionId() {
+ final InstanceId id = mSession.get(StatusBarManager.SESSION_KEYGUARD);
+ return id != null ? id.getId() : null;
+ }
+
+ @Nullable
+ @Override
+ public Integer getBiometricPromptSessionId() {
+ final InstanceId id = mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT);
+ return id != null ? id.getId() : null;
+ }
+
+ @Override
+ public boolean isAoD() {
+ return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void subscribe(@NonNull OperationContext context,
+ @NonNull Consumer<OperationContext> consumer) {
+ mSubscribers.put(context, consumer);
+ }
+
+ @Override
+ public void unsubscribe(@NonNull OperationContext context) {
+ mSubscribers.remove(context);
+ }
+
+ private void notifySubscribers() {
+ mSubscribers.forEach((context, consumer) -> {
+ context.isAoD = isAoD();
+ consumer.accept(context);
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index c3471bd..8965227 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics.log;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -33,42 +35,49 @@
private BiometricFrameworkStatsLogger() {}
+ /** Shared instance. */
public static BiometricFrameworkStatsLogger getInstance() {
return sInstance;
}
/** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */
- public void acquired(
+ public void acquired(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug,
- int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+ int acquiredInfo, int vendorCode, int targetUserId) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsAction,
statsClient,
acquiredInfo,
vendorCode,
isDebug,
- -1 /* sensorId */);
+ -1 /* sensorId */,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
- public void authenticate(
+ public void authenticate(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto,
- int targetUserId, boolean isBiometricPrompt, float ambientLightLux) {
+ int authState, boolean requireConfirmation,
+ int targetUserId, float ambientLightLux) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsClient,
requireConfirmation,
authState,
sanitizeLatency(latency),
isDebug,
-1 /* sensorId */,
- ambientLightLux);
+ ambientLightLux,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
@@ -84,20 +93,23 @@
}
/** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
- public void error(
+ public void error(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
- int error, int vendorCode, boolean isCrypto, int targetUserId) {
+ int error, int vendorCode, int targetUserId) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
statsModality,
targetUserId,
- isCrypto,
+ operationContext.isCrypto,
statsAction,
statsClient,
error,
vendorCode,
isDebug,
sanitizeLatency(latency),
- -1 /* sensorId */);
+ -1 /* sensorId */,
+ operationContext.id,
+ sessionType(operationContext.reason),
+ operationContext.isAoD);
}
/** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
@@ -123,4 +135,14 @@
}
return latency;
}
+
+ private static int sessionType(@OperationReason byte reason) {
+ if (reason == OperationReason.BIOMETRIC_PROMPT) {
+ return BiometricsProtoEnums.SESSION_TYPE_BIOMETRIC_PROMPT;
+ }
+ if (reason == OperationReason.KEYGUARD) {
+ return BiometricsProtoEnums.SESSION_TYPE_KEYGUARD_ENTRY;
+ }
+ return BiometricsProtoEnums.SESSION_TYPE_UNKNOWN;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index d029af3..2a8d9f1 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -25,6 +25,7 @@
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.util.Slog;
@@ -79,6 +80,12 @@
}
};
+ /** Get a new logger with all unknown fields (for operations that do not require logs). */
+ public static BiometricLogger ofUnknown(@NonNull Context context) {
+ return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ }
+
/**
* @param context system_server context
* @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants.
@@ -103,6 +110,11 @@
mSensorManager = sensorManager;
}
+ /** Creates a new logger with the action replaced with the new action. */
+ public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
+ return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+ }
+
/** Disable logging metrics and only log critical events, such as system health issues. */
public void disableMetrics() {
mShouldLogMetrics = false;
@@ -133,8 +145,8 @@
}
/** Log an acquisition event. */
- public void logOnAcquired(Context context,
- int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) {
+ public void logOnAcquired(Context context, OperationContext operationContext,
+ int acquiredInfo, int vendorCode, int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
@@ -154,7 +166,7 @@
if (DEBUG) {
Slog.v(TAG, "Acquired! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", AcquiredInfo: " + acquiredInfo
@@ -165,14 +177,14 @@
return;
}
- mSink.acquired(mStatsModality, mStatsAction, mStatsClient,
+ mSink.acquired(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- acquiredInfo, vendorCode, isCrypto, targetUserId);
+ acquiredInfo, vendorCode, targetUserId);
}
/** Log an error during an operation. */
- public void logOnError(Context context,
- int error, int vendorCode, boolean isCrypto, int targetUserId) {
+ public void logOnError(Context context, OperationContext operationContext,
+ int error, int vendorCode, int targetUserId) {
if (!mShouldLogMetrics) {
return;
}
@@ -183,7 +195,7 @@
if (DEBUG) {
Slog.v(TAG, "Error! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Action: " + mStatsAction
+ ", Client: " + mStatsClient
+ ", Error: " + error
@@ -197,14 +209,14 @@
return;
}
- mSink.error(mStatsModality, mStatsAction, mStatsClient,
+ mSink.error(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId), latency,
- error, vendorCode, isCrypto, targetUserId);
+ error, vendorCode, targetUserId);
}
/** Log authentication attempt. */
- public void logOnAuthenticated(Context context,
- boolean authenticated, boolean requireConfirmation, boolean isCrypto,
+ public void logOnAuthenticated(Context context, OperationContext operationContext,
+ boolean authenticated, boolean requireConfirmation,
int targetUserId, boolean isBiometricPrompt) {
if (!mShouldLogMetrics) {
return;
@@ -230,7 +242,7 @@
if (DEBUG) {
Slog.v(TAG, "Authenticated! Modality: " + mStatsModality
+ ", User: " + targetUserId
- + ", IsCrypto: " + isCrypto
+ + ", IsCrypto: " + operationContext.isCrypto
+ ", Client: " + mStatsClient
+ ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState
@@ -244,10 +256,9 @@
return;
}
- mSink.authenticate(mStatsModality, mStatsAction, mStatsClient,
+ mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId),
- latency, authenticated, authState, requireConfirmation, isCrypto,
- targetUserId, isBiometricPrompt, mLastAmbientLux);
+ latency, authState, requireConfirmation, targetUserId, mLastAmbientLux);
}
/** Log enrollment outcome. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 86d72ba..0f0032b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -29,6 +29,11 @@
import android.os.Vibrator;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
+
/**
* Abstract {@link HalClientMonitor} subclass that operations eligible/interested in acquisition
* messages should extend.
@@ -52,21 +57,21 @@
private boolean mShouldSendErrorToClient = true;
private boolean mAlreadyCancelled;
+ public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
+ @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId,
+ logger, biometricContext);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ mShouldVibrate = shouldVibrate;
+ }
+
/**
* Stops the HAL operation specific to the ClientMonitor subclass.
*/
protected abstract void stopHalOperation();
- public AcquisitionClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate,
- int statsModality, int statsAction, int statsClient) {
- super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
- statsAction, statsClient);
- mPowerManager = context.getSystemService(PowerManager.class);
- mShouldVibrate = shouldVibrate;
- }
-
@Override
public void unableToStart() {
try {
@@ -105,8 +110,8 @@
// that do not handle lockout under the HAL. In these cases, ensure that the framework only
// sends errors once per ClientMonitor.
if (mShouldSendErrorToClient) {
- getLogger().logOnError(getContext(), errorCode, vendorCode,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ errorCode, vendorCode, getTargetUserId());
try {
if (getListener() != null) {
mShouldSendErrorToClient = false;
@@ -164,8 +169,8 @@
protected final void onAcquiredInternal(int acquiredInfo, int vendorCode,
boolean shouldSend) {
- getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnAcquired(getContext(), getOperationContext(),
+ acquiredInfo, vendorCode, getTargetUserId());
if (DEBUG) {
Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode
+ ", shouldSend: " + shouldSend);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 35a0f57..54b79e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -29,7 +29,6 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -39,9 +38,12 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
/**
* A class to keep track of the authentication state for a given client.
@@ -89,32 +91,16 @@
// the state. We should think of a way to improve this in the future.
protected @State int mState = STATE_NEW;
- /**
- * Handles lifecycle, e.g. {@link BiometricScheduler},
- * {@link ClientMonitorCallback} after authentication
- * results are known. Note that this happens asynchronously from (but shortly after)
- * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows
- * {@link CoexCoordinator} a chance to invoke/delay this event.
- * @param authenticated
- */
- protected abstract void handleLifecycleAfterAuth(boolean authenticated);
-
- /**
- * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
- * etc)
- */
- public abstract boolean wasUserDetected();
-
- public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
- int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
- int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
+ int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
- shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE,
- statsClient);
+ shouldVibrate, biometricLogger, biometricContext);
mIsStrongBiometric = isStrongBiometric;
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
@@ -180,8 +166,8 @@
@Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
- isCryptoOperation(), getTargetUserId(), isBiometricPrompt());
+ getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+ authenticated, mRequireConfirmation, getTargetUserId(), isBiometricPrompt());
final ClientMonitorCallbackConverter listener = getListener();
@@ -474,6 +460,22 @@
}
}
+ /**
+ * Handles lifecycle, e.g. {@link BiometricScheduler},
+ * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication
+ * results are known. Note that this happens asynchronously from (but shortly after)
+ * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows
+ * {@link CoexCoordinator} a chance to invoke/delay this event.
+ * @param authenticated
+ */
+ protected abstract void handleLifecycleAfterAuth(boolean authenticated);
+
+ /**
+ * @return true if a user was detected (i.e. face was found, fingerprint sensor was touched.
+ * etc)
+ */
+ public abstract boolean wasUserDetected();
+
public @State int getState() {
return mState;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e1f7e2a..1b2e606 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -21,12 +21,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import java.util.NoSuchElementException;
@@ -50,6 +50,7 @@
@NonNull private final String mOwner;
private final int mSensorId; // sensorId as configured by the framework
@NonNull private final BiometricLogger mLogger;
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IBinder mToken;
private long mRequestId;
@@ -82,22 +83,13 @@
* @param owner name of the client that owns this
* @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
* @param sensorId ID of the sensor that the operation should be requested of
- * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
- * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants
- * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants
+ * @param logger framework stats logger
+ * @param biometricContext system context metadata
*/
public BaseClientMonitor(@NonNull Context context,
@Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
- int statsClient) {
- this(context, token, listener, userId, owner, cookie, sensorId,
- new BiometricLogger(context, statsModality, statsAction, statsClient));
- }
-
- @VisibleForTesting
- BaseClientMonitor(@NonNull Context context,
- @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) {
+ @NonNull String owner, int cookie, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
mSequentialId = sCount++;
mContext = context;
mToken = token;
@@ -108,6 +100,7 @@
mCookie = cookie;
mSensorId = sensorId;
mLogger = logger;
+ mBiometricContext = biometricContext;
try {
if (token != null) {
@@ -207,20 +200,29 @@
return false;
}
+ /** System context that may change during operations. */
+ @NonNull
+ protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
/** Logger for this client */
@NonNull
public BiometricLogger getLogger() {
return mLogger;
}
+ @NonNull
public final Context getContext() {
return mContext;
}
+ @NonNull
public final String getOwnerString() {
return mOwner;
}
+ @Nullable
public final ClientMonitorCallbackConverter getListener() {
return mListener;
}
@@ -229,6 +231,7 @@
return mTargetUserId;
}
+ @Nullable
public final IBinder getToken() {
return mToken;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 3b7adc1..483ce75 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -20,15 +20,17 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.FingerprintManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Arrays;
+import java.util.function.Supplier;
/**
* A class to keep track of the enrollment state for a given client.
@@ -49,13 +51,13 @@
*/
protected abstract boolean hasReachedEnrollmentLimit();
- public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
- int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) {
+ int timeoutSec, int sensorId, boolean shouldVibrate,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ shouldVibrate, logger, biometricContext);
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 6fb6d08..2adf0cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -18,23 +18,26 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> {
private static final String TAG = "GenerateChallengeClient";
- public GenerateChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
- int userId, @NonNull String owner, int sensorId) {
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ biometricLogger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index c8830f8..a6e8911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -19,22 +19,50 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
+
/**
* Abstract {@link BaseClientMonitor} implementation that supports HAL operations.
* @param <T> HAL template
*/
public abstract class HalClientMonitor<T> extends BaseClientMonitor {
+
+ @NonNull
+ protected final Supplier<T> mLazyDaemon;
+
+ @NonNull
+ private final OperationContext mOperationContext = new OperationContext();
+
/**
- * Interface that allows ClientMonitor subclasses to retrieve a fresh instance to the HAL.
+ * @param context system_server context
+ * @param lazyDaemon pointer for lazy retrieval of the HAL
+ * @param token a unique token for the client
+ * @param listener recipient of related events (e.g. authentication)
+ * @param userId target user id for operation
+ * @param owner name of the client that owns this
+ * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
+ * @param sensorId ID of the sensor that the operation should be requested of
+ * @param biometricLogger framework stats logger
+ * @param biometricContext system context metadata
*/
- public interface LazyDaemon<T> {
- /**
- * @return A fresh instance to the biometric HAL
- */
- T getDaemon();
+ public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
+ @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int cookie, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+ super(context, token, listener, userId, owner, cookie, sensorId,
+ biometricLogger, biometricContext);
+ mLazyDaemon = lazyDaemon;
+ }
+
+ @Nullable
+ public T getFreshDaemon() {
+ return mLazyDaemon.get();
}
/**
@@ -49,33 +77,28 @@
*/
public abstract void unableToStart();
- @NonNull
- protected final LazyDaemon<T> mLazyDaemon;
+ @Override
+ public void destroy() {
+ super.destroy();
- /**
- * @param context system_server context
- * @param lazyDaemon pointer for lazy retrieval of the HAL
- * @param token a unique token for the client
- * @param listener recipient of related events (e.g. authentication)
- * @param userId target user id for operation
- * @param owner name of the client that owns this
- * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon)
- * @param sensorId ID of the sensor that the operation should be requested of
- * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants
- * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants
- * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants
- */
- public HalClientMonitor(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
- int statsClient) {
- super(context, token, listener, userId, owner, cookie, sensorId, statsModality,
- statsAction, statsClient);
- mLazyDaemon = lazyDaemon;
+ // subclasses should do this earlier in most cases, but ensure it happens now
+ unsubscribeBiometricContext();
}
- @Nullable
- public T getFreshDaemon() {
- return mLazyDaemon.getDaemon();
+ protected OperationContext getOperationContext() {
+ return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
+ }
+
+ protected ClientMonitorCallback getBiometricContextUnsubscriber() {
+ return new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor monitor, boolean success) {
+ unsubscribeBiometricContext();
+ }
+ };
+ }
+
+ protected void unsubscribeBiometricContext() {
+ getBiometricContext().unsubscribe(mOperationContext);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 0636893..57ea812 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -19,15 +19,17 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Wraps {@link InternalEnumerateClient} and {@link RemovalClient}. Keeps track of all the
@@ -99,20 +101,23 @@
};
protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context,
- LazyDaemon<T> lazyDaemon, IBinder token, int userId, String owner,
- List<S> enrolledList, BiometricUtils<S> utils, int sensorId);
+ Supplier<T> lazyDaemon, IBinder token, int userId, String owner,
+ List<S> enrolledList, BiometricUtils<S> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext);
protected abstract RemovalClient<S, T> getRemovalClient(Context context,
- LazyDaemon<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
- BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds);
+ Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner,
+ BiometricUtils<S> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ Map<Integer, Long> authenticatorIds);
- protected InternalCleanupClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- int userId, @NonNull String owner, int sensorId, int statsModality,
+ protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */,
- userId, owner, 0 /* cookie */, sensorId, statsModality,
- BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ userId, owner, 0 /* cookie */, sensorId, logger, biometricContext);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
@@ -126,7 +131,8 @@
mUnknownHALTemplates.remove(template);
mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
template.mIdentifier.getBiometricId(), template.mUserId,
- getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds);
+ getContext().getPackageName(), mBiometricUtils, getSensorId(),
+ getLogger(), getBiometricContext(), mAuthenticatorIds);
getLogger().logUnknownEnrollmentInHal();
@@ -144,7 +150,8 @@
// Start enumeration. Removal will start if necessary, when enumeration is completed.
mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
- getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+ getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(),
+ getBiometricContext());
Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
mCurrentTask.start(mEnumerateCallback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 05ea19a..7f8f38f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -19,14 +19,16 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
/**
* Internal class to help clean up unknown templates in the HAL and Framework
@@ -43,15 +45,17 @@
// List of templates to remove from the HAL
private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>();
- protected InternalEnumerateClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, int userId, @NonNull String owner,
@NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList,
- @NonNull BiometricUtils utils, int sensorId, int statsModality) {
+ @NonNull BiometricUtils utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
// Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
- 0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
+ //, BiometricsProtoEnums.ACTION_ENUMERATE,
+ // BiometricsProtoEnums.CLIENT_UNKNOWN);
mEnrolledList = enrolledList;
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index ee6bb0f..d5aa5e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -19,14 +19,16 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IInvalidationCallback;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Map;
+import java.util.function.Supplier;
/**
* ClientMonitor subclass for requesting authenticatorId invalidation. See
@@ -40,13 +42,14 @@
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@NonNull private final IInvalidationCallback mInvalidationCallback;
- public InvalidationClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds,
+ public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
+ int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds,
@NonNull IInvalidationCallback callback) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId,
context.getOpPackageName(), 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mAuthenticatorIds = authenticatorIds;
mInvalidationCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index b2661a2..1097bb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -20,10 +20,11 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IInvalidationCallback;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
/**
* ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other
@@ -74,11 +75,10 @@
};
public InvalidationRequesterClient(@NonNull Context context, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull BiometricUtils<S> utils) {
super(context, null /* token */, null /* listener */, userId,
- context.getOpPackageName(), 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ context.getOpPackageName(), 0 /* cookie */, sensorId, logger, biometricContext);
mBiometricManager = context.getSystemService(BiometricManager.class);
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index e79819b..07ce841 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -19,14 +19,16 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import java.util.Map;
+import java.util.function.Supplier;
/**
* A class to keep track of the remove state for a given client.
@@ -40,13 +42,15 @@
private final Map<Integer, Long> mAuthenticatorIds;
private final boolean mHasEnrollmentsBeforeStarting;
- public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId,
- @NonNull Map<Integer, Long> authenticatorIds, int statsModality) {
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- statsModality, BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
+ //, BiometricsProtoEnums.ACTION_REMOVE,
+ // BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 21a6ddf..88f4da2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -18,18 +18,21 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> {
- public RevokeChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
- @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) {
+ public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
+ @NonNull IBinder token, int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 3d69326..21c9f64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -19,11 +19,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
/**
* Abstract class for starting a new user.
@@ -37,18 +40,18 @@
* @param <U> New user object.
*/
public interface UserStartedCallback<U> {
- void onUserStarted(int newUserId, U newUser);
+ void onUserStarted(int newUserId, U newUser, int halInterfaceVersion);
}
@NonNull @VisibleForTesting
protected final UserStartedCallback<U> mUserStartedCallback;
- public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStartedCallback<U> callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mUserStartedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 1f6e1e9..e8654dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -19,11 +19,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
+import java.util.function.Supplier;
/**
* Abstract class for stopping a user.
@@ -43,12 +46,12 @@
getCallback().onClientFinished(this, true /* success */);
}
- public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStoppedCallback callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mUserStoppedCallback = callback;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 039b08e..2e82057 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -57,6 +57,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -645,7 +646,7 @@
try {
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
- mLockoutResetDispatcher);
+ mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
new file mode 100644
index 0000000..29eee6b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.face.ISession;
+
+/**
+ * A holder for an AIDL {@link ISession} with additional metadata about the current user
+ * and the backend.
+ */
+public class AidlSession {
+
+ private final int mHalInterfaceVersion;
+ @NonNull
+ private final ISession mSession;
+ private final int mUserId;
+ @NonNull private final HalSessionCallback mHalSessionCallback;
+
+ public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
+ HalSessionCallback halSessionCallback) {
+ mHalInterfaceVersion = halInterfaceVersion;
+ mSession = session;
+ mUserId = userId;
+ mHalSessionCallback = halSessionCallback;
+ }
+
+ /** The underlying {@link ISession}. */
+ @NonNull public ISession getSession() {
+ return mSession;
+ }
+
+ /** The user id associated with the session. */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
+ HalSessionCallback getHalSessionCallback() {
+ return mHalSessionCallback;
+ }
+
+ /**
+ * If this backend implements the *WithContext methods for enroll, authenticate, and
+ * detectInteraction. These variants should always be called if they are available.
+ */
+ public boolean hasContextMethods() {
+ return mHalInterfaceVersion >= 2;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 8998269..9bd7476 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -165,7 +165,7 @@
}
mEnrollmentIds.add(nextRandomId);
- mSensor.getSessionForUser(userId).mHalSessionCallback
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
.onEnrollmentProgress(nextRandomId, 0 /* remaining */);
}
@@ -181,7 +181,7 @@
return;
}
final int fid = faces.get(0).getBiometricId();
- mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationSucceeded(fid,
HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
}
@@ -189,7 +189,7 @@
public void rejectAuthentication(int userId) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
- mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
// TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@@ -205,7 +205,7 @@
// TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
// This will need to call the correct callback once the onAcquired callback is removed.
- mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame(
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
authenticationFrame);
}
@@ -213,7 +213,7 @@
public void notifyError(int userId, int errorCode) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
- mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
0 /* vendorCode */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index dc21a04..7765ab3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -25,10 +25,8 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceManager;
import android.os.IBinder;
@@ -36,7 +34,10 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -48,11 +49,13 @@
import com.android.server.biometrics.sensors.face.UsageStats;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Face-specific authentication client for the {@link IFace} AIDL HAL interface.
*/
-class FaceAuthenticationClient extends AuthenticationClient<ISession> implements LockoutConsumer {
+class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
+ implements LockoutConsumer {
private static final String TAG = "FaceAuthenticationClient";
@NonNull private final UsageStats mUsageStats;
@@ -69,23 +72,40 @@
@FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
- boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
boolean isKeyguardBypassEnabled) {
+ this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
+ restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
+ isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class));
+ }
+
+ @VisibleForTesting
+ FaceAuthenticationClient(@NonNull Context context,
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+ boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull UsageStats usageStats,
+ @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
- lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, null /* taskStackListener */, lockoutCache,
+ allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mSensorPrivacyManager = sensorPrivacyManager;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -122,7 +142,7 @@
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
} else {
- mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ mCancellationSignal = doAuthenticate();
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -131,6 +151,17 @@
}
}
+ private ICancellationSignal doAuthenticate() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ return session.getSession().authenticateWithContext(
+ mOperationId, getOperationContext());
+ } else {
+ return session.getSession().authenticate(mOperationId);
+ }
+ }
+
@Override
protected void stopHalOperation() {
if (mCancellationSignal != null) {
@@ -244,8 +275,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -260,8 +291,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 72a20db07..efedcf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -21,24 +21,27 @@
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.DetectionConsumer;
+import java.util.function.Supplier;
+
/**
* Performs face detection without exposing any matching information (e.g. accept/reject have the
* same haptic, lockout counter is not increased).
*/
-public class FaceDetectClient extends AcquisitionClient<ISession> implements DetectionConsumer {
+public class FaceDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
private static final String TAG = "FaceDetectClient";
@@ -46,16 +49,29 @@
@Nullable private ICancellationSignal mCancellationSignal;
@Nullable private SensorPrivacyManager mSensorPrivacyManager;
- public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric) {
+ this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+ logger, biometricContext, isStrongBiometric,
+ context.getSystemService(SensorPrivacyManager.class));
+ }
+
+ @VisibleForTesting
+ FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mSensorPrivacyManager = sensorPrivacyManager;
}
@Override
@@ -87,13 +103,23 @@
}
try {
- mCancellationSignal = getFreshDaemon().detectInteraction();
+ mCancellationSignal = doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting face detect", e);
mCallback.onClientFinished(this, false /* success */);
}
}
+ private ICancellationSignal doDetectInteraction() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ return session.getSession().detectInteractionWithContext(getOperationContext());
+ } else {
+ return session.getSession().detectInteraction();
+ }
+ }
+
@Override
public void onInteractionDetected() {
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 5c57dbb..da78536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -20,16 +20,15 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.EnrollmentType;
import android.hardware.biometrics.face.Feature;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.common.NativeHandle;
import android.hardware.face.Face;
import android.hardware.face.FaceEnrollFrame;
import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -38,6 +37,8 @@
import com.android.internal.R;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
@@ -51,11 +52,12 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
/**
* Face-specific enroll client for the {@link IFace} AIDL HAL interface.
*/
-public class FaceEnrollClient extends EnrollClient<ISession> {
+public class FaceEnrollClient extends EnrollClient<AidlSession> {
private static final String TAG = "FaceEnrollClient";
@@ -82,15 +84,15 @@
}
};
- FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
- boolean debugConsent) {
+ @Nullable Surface previewSurface, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int maxTemplatesPerUser, boolean debugConsent) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
- false /* shouldVibrate */);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
@@ -177,14 +179,12 @@
featureList.add(Feature.REQUIRE_DIVERSE_POSES);
}
- byte[] features = new byte[featureList.size()];
+ final byte[] features = new byte[featureList.size()];
for (int i = 0; i < featureList.size(); i++) {
features[i] = featureList.get(i);
}
- mCancellationSignal = getFreshDaemon().enroll(
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
- EnrollmentType.DEFAULT, features, mHwPreviewHandle);
+ mCancellationSignal = doEnroll(features);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Exception when requesting enroll", e);
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -192,6 +192,20 @@
}
}
+ private ICancellationSignal doEnroll(byte[] features) throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+ final HardwareAuthToken hat =
+ HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+ if (session.hasContextMethods()) {
+ return session.getSession().enrollWithContext(
+ hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, getOperationContext());
+ } else {
+ return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
+ mHwPreviewHandle);
+ }
+ }
+
@Override
protected void stopHalOperation() {
if (mCancellationSignal != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 7cdeebb..165c3a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -19,31 +19,36 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
+import java.util.function.Supplier;
+
/**
* Face-specific generateChallenge client for the {@link IFace} AIDL HAL interface.
*/
-public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISession> {
+public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FaceGenerateChallengeClient";
FaceGenerateChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().generateChallenge();
+ getFreshDaemon().getSession().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index 584b58c..1f4f612 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -18,29 +18,31 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.face.ISession;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import java.util.Map;
+import java.util.function.Supplier;
-class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> {
+class FaceGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
private static final String TAG = "FaceGetAuthenticatorIdClient";
private final Map<Integer, Long> mAuthenticatorIds;
- FaceGetAuthenticatorIdClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceGetAuthenticatorIdClient(@NonNull Context context,
+ @NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String opPackageName, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mAuthenticatorIds = authenticatorIds;
}
@@ -57,7 +59,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getAuthenticatorId();
+ getFreshDaemon().getSession().getAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index acf5720..ef3b345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -20,15 +20,15 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -36,22 +36,23 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Face-specific get feature client for the {@link IFace} AIDL HAL interface.
*/
-public class FaceGetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
private static final String TAG = "FaceGetFeatureClient";
private final int mUserId;
- FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId) {
+ @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mUserId = userId;
}
@@ -69,7 +70,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getFeatures();
+ getFreshDaemon().getSession().getFeatures();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index c6696aed..54f2033 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -18,12 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -31,37 +31,41 @@
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
*/
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, ISession> {
+class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
FaceInternalCleanupClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+ @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+ @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
enrolledList, utils, authenticatorIds);
}
@Override
- protected InternalEnumerateClient<ISession> getEnumerateClient(Context context,
- LazyDaemon<ISession> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+ protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
+ Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
+ List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
- protected RemovalClient<Face, ISession> getRemovalClient(Context context,
- LazyDaemon<ISession> lazyDaemon, IBinder token,
+ protected RemovalClient<Face, AidlSession> getRemovalClient(Context context,
+ Supplier<AidlSession> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FaceRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
- utils, sensorId, authenticatorIds);
+ utils, sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
index 0ece884..d85455e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java
@@ -18,37 +18,39 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import java.util.List;
+import java.util.function.Supplier;
/**
* Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface.
*/
-class FaceInternalEnumerateClient extends InternalEnumerateClient<ISession> {
+class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
private static final String TAG = "FaceInternalEnumerateClient";
FaceInternalEnumerateClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, int userId,
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId) {
+ @NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().enumerateEnrollments();
+ getFreshDaemon().getSession().enumerateEnrollments();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enumerate", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
index 405e2b2..39d8de0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java
@@ -19,28 +19,32 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.InvalidationClient;
import java.util.Map;
+import java.util.function.Supplier;
-public class FaceInvalidationClient extends InvalidationClient<Face, ISession> {
+public class FaceInvalidationClient extends InvalidationClient<Face, AidlSession> {
private static final String TAG = "FaceInvalidationClient";
public FaceInvalidationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+ @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
- super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+ super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+ authenticatorIds, callback);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().invalidateAuthenticatorId();
+ getFreshDaemon().getSession().invalidateAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 9d7a552..4e03ee9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -23,6 +23,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
@@ -47,6 +48,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -87,7 +90,7 @@
@NonNull private final BiometricTaskStackListener mTaskStackListener;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IFace mDaemon;
private final class BiometricTaskStackListener extends TaskStackListener {
@@ -125,7 +128,8 @@
public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
@NonNull String halInstanceName,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
@@ -134,6 +138,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
+ mBiometricContext = biometricContext;
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -153,7 +158,7 @@
prop.supportsDetectInteraction, prop.halControlsPreview,
false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher);
+ internalProp, lockoutResetDispatcher, mBiometricContext);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);
@@ -237,6 +242,9 @@
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
@@ -247,6 +255,8 @@
mHandler.post(() -> {
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
+ BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
FaceUtils.getInstance(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -285,6 +295,9 @@
mHandler.post(() -> {
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
@@ -311,7 +324,10 @@
mHandler.post(() -> {
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId);
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -322,7 +338,9 @@
mHandler.post(() -> {
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
- challenge);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
}
@@ -340,8 +358,10 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
- debugConsent);
+ ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, maxTemplatesPerUser, debugConsent);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -372,8 +392,9 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
mSensors.get(sensorId).getLazySession(),
- token, id, callback, userId, opPackageName,
- sensorId, isStrongBiometric, statsClient);
+ token, id, callback, userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client);
});
@@ -396,7 +417,9 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
userId, operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ false /* requireConfirmation */, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled);
scheduleForSensor(sensorId, client);
@@ -450,6 +473,9 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
@@ -460,7 +486,10 @@
mHandler.post(() -> {
final FaceResetLockoutClient client = new FaceResetLockoutClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken,
mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
scheduleForSensor(sensorId, client);
@@ -481,7 +510,9 @@
final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId,
- mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken);
+ mContext.getOpPackageName(), sensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, enabled, hardwareAuthToken);
scheduleForSensor(sensorId, client);
});
}
@@ -498,7 +529,8 @@
}
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
mSensors.get(sensorId).getLazySession(), token, callback, userId,
- mContext.getOpPackageName(), sensorId);
+ mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -518,13 +550,21 @@
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FaceUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, callback);
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+ statsAction, statsClient);
+ }
+
@Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index ba678f3..0512017 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -18,42 +18,44 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Face-specific removal client for the {@link IFace} AIDL HAL interface.
*/
-class FaceRemovalClient extends RemovalClient<Face, ISession> {
+class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
private static final String TAG = "FaceRemovalClient";
final int[] mBiometricIds;
- FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int[] biometricIds, int userId, @NonNull String owner,
@NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext, authenticatorIds);
mBiometricIds = biometricIds;
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().removeEnrollments(mBiometricIds);
+ getFreshDaemon().getSession().removeEnrollments(mBiometricIds);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting remove", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index fd44c5c..de0a36a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -18,15 +18,15 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -34,12 +34,14 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import java.util.function.Supplier;
+
/**
* Face-specific resetLockout client for the {@link IFace} AIDL HAL interface.
* Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
* cleared.
*/
-public class FaceResetLockoutClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
private static final String TAG = "FaceResetLockoutClient";
@@ -48,12 +50,12 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
FaceResetLockoutClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -73,7 +75,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().resetLockout(mHardwareAuthToken);
+ getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 7a69c44..8838345 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -19,33 +19,38 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
+import java.util.function.Supplier;
+
/**
* Face-specific revokeChallenge client for the {@link IFace} AIDL HAL interface.
*/
-public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> {
+public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
private static final String TAG = "FaceRevokeChallengeClient";
private final long mChallenge;
FaceRevokeChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId, long challenge) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ long challenge) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
mChallenge = challenge;
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().revokeChallenge(mChallenge);
+ getFreshDaemon().getSession().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index ee6982a..6c14387 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -18,9 +18,7 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
@@ -28,15 +26,19 @@
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
+import java.util.function.Supplier;
+
/**
* Face-specific get feature client for the {@link IFace} AIDL HAL interface.
*/
-public class FaceSetFeatureClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
private static final String TAG = "FaceSetFeatureClient";
@@ -44,13 +46,13 @@
private final boolean mEnabled;
private final HardwareAuthToken mHardwareAuthToken;
- FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, boolean enabled,
- byte[] hardwareAuthToken) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, boolean enabled, byte[] hardwareAuthToken) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mEnabled = enabled;
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
@@ -74,8 +76,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon()
- .setFeature(mHardwareAuthToken,
+ getFreshDaemon().getSession().setFeature(mHardwareAuthToken,
AidlConversionUtils.convertFrameworkToAidlFeature(mFeature), mEnabled);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index 4a3da0d..61e7ab7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -27,19 +27,25 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StartUserClient;
+import java.util.function.Supplier;
+
public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
private static final String TAG = "FaceStartUserClient";
@NonNull private final ISessionCallback mSessionCallback;
- public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon,
+ public FaceStartUserClient(@NonNull Context context,
+ @NonNull Supplier<IFace> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull ISessionCallback sessionCallback,
@NonNull UserStartedCallback<ISession> callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mSessionCallback = sessionCallback;
}
@@ -52,10 +58,12 @@
@Override
protected void startHalOperation() {
try {
- final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ final IFace hal = getFreshDaemon();
+ final int version = hal.getInterfaceVersion();
+ final ISession newSession = hal.createSession(getSensorId(),
getTargetUserId(), mSessionCallback);
Binder.allowBlocking(newSession.asBinder());
- mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession, version);
getCallback().onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -65,6 +73,5 @@
@Override
public void unableToStart() {
-
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 88b9235..0110ae9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -19,21 +19,25 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StopUserClient;
-public class FaceStopUserClient extends StopUserClient<ISession> {
+import java.util.function.Supplier;
+
+public class FaceStopUserClient extends StopUserClient<AidlSession> {
private static final String TAG = "FaceStopUserClient";
- public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
@@ -45,7 +49,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().close();
+ getFreshDaemon().getSession().close();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
getCallback().onClientFinished(this, false /* success */);
@@ -54,6 +58,5 @@
@Override
public void unableToStart() {
-
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 206b8f0..b69c760 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -42,18 +42,20 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.Interruptable;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
@@ -67,6 +69,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Maintains the state of a single sensor within an instance of the {@link IFace} HAL.
@@ -84,26 +87,12 @@
@NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
- @Nullable private Session mCurrentSession;
- static class Session {
- @NonNull final HalSessionCallback mHalSessionCallback;
- @NonNull private final String mTag;
- @NonNull private final ISession mSession;
- private final int mUserId;
+ @NonNull private final Supplier<AidlSession> mLazySession;
+ @Nullable private AidlSession mCurrentSession;
- Session(@NonNull String tag, @NonNull ISession session, int userId,
- @NonNull HalSessionCallback halSessionCallback) {
- mTag = tag;
- mSession = session;
- mUserId = userId;
- mHalSessionCallback = halSessionCallback;
- Slog.d(mTag, "New session created for user: " + userId);
- }
- }
-
- static class HalSessionCallback extends ISessionCallback.Stub {
+ @VisibleForTesting
+ public static class HalSessionCallback extends ISessionCallback.Stub {
/**
* Interface to sends results to the HalSessionCallback's owner.
*/
@@ -487,7 +476,8 @@
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -496,33 +486,36 @@
mSensorProperties = sensorProperties;
mScheduler = new UserAwareBiometricScheduler(tag,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
- () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
- mSensorProperties.sensorId, () -> mCurrentSession = null);
+ mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
+ () -> mCurrentSession = null);
}
@NonNull
@Override
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
-
final int sensorId = mSensorProperties.sensorId;
final HalSessionCallback resultController = new HalSessionCallback(mContext,
mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher, callback);
+ lockoutResetDispatcher, () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
- (userIdStarted, newSession) -> {
- mCurrentSession = new Session(mTag, newSession, userIdStarted,
- resultController);
+ (userIdStarted, newSession, halInterfaceVersion) -> {
+ Slog.d(mTag, "New session created for user: "
+ + userIdStarted + " with hal version: "
+ + halInterfaceVersion);
+ mCurrentSession = new AidlSession(halInterfaceVersion,
+ newSession, userIdStarted, resultController);
if (FaceUtils.getLegacyInstance(sensorId)
.isInvalidationInProgress(mContext, userIdStarted)) {
Slog.w(mTag,
@@ -537,15 +530,16 @@
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
resultController, userStartedCallback);
}
});
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
}
- @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
+ @NonNull Supplier<AidlSession> getLazySession() {
return mLazySession;
}
@@ -553,8 +547,8 @@
return mSensorProperties;
}
- @Nullable Session getSessionForUser(int userId) {
- if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+ @Nullable AidlSession getSessionForUser(int userId) {
+ if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
return null;
@@ -583,10 +577,10 @@
if (enabled != mTestHalEnabled) {
// The framework should retrieve a new session from the HAL.
try {
- if (mCurrentSession != null && mCurrentSession.mSession != null) {
+ if (mCurrentSession != null) {
// TODO(181984005): This should be scheduled instead of directly invoked
Slog.d(mTag, "Closing old session");
- mCurrentSession.mSession.close();
+ mCurrentSession.getSession().close();
}
} catch (RemoteException e) {
Slog.e(mTag, "RemoteException", e);
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/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 9a52db1..73c759f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -55,6 +55,8 @@
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -64,7 +66,6 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -89,6 +90,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/**
* Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -111,12 +113,13 @@
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
- @NonNull private final HalClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+ @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
@NonNull private final LockoutHalImpl mLockoutTracker;
@NonNull private final UsageStats mUsageStats;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
+ @NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -153,6 +156,7 @@
@NonNull private final LockoutHalImpl mLockoutTracker;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+
HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
@NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
@@ -335,12 +339,14 @@
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
- @NonNull BiometricScheduler scheduler) {
+ @NonNull BiometricScheduler scheduler,
+ @NonNull BiometricContext biometricContext) {
mSensorProperties = sensorProps;
mContext = context;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
+ mBiometricContext = biometricContext;
mUsageStats = new UsageStats(context);
mAuthenticatorIds = new HashMap<>();
mLazyDaemon = Face10.this::getDaemon;
@@ -365,7 +371,8 @@
final Handler handler = new Handler(Looper.getMainLooper());
return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityTracker */));
+ null /* gestureAvailabilityTracker */),
+ BiometricContext.getInstance(context));
}
@Override
@@ -533,7 +540,10 @@
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, sSystemClock.millis());
+ opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, sSystemClock.millis());
mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -562,7 +572,10 @@
mGeneratedChallengeCache = null;
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mLazyDaemon, token, userId, opPackageName, mSensorId);
+ mLazyDaemon, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -590,7 +603,10 @@
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
+ ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -637,8 +653,9 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
opPackageName, cookie, false /* requireConfirmation */, mSensorId,
- isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, isKeyguardBypassEnabled);
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric, mLockoutTracker,
+ mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -670,7 +687,10 @@
final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -685,7 +705,10 @@
final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds);
+ FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -702,7 +725,9 @@
final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- hardwareAuthToken);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -723,7 +748,9 @@
final int faceId = faces.get(0).getBiometricId();
final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+ opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
+ feature, enabled, hardwareAuthToken, faceId);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -742,7 +769,9 @@
final int faceId = faces.get(0).getBiometricId();
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
- token, listener, userId, opPackageName, mSensorId, feature, faceId);
+ token, listener, userId, opPackageName, mSensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, faceId);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(
@@ -767,7 +796,10 @@
final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId);
final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
});
@@ -890,7 +922,9 @@
final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
- hasEnrolled, mAuthenticatorIds);
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hasEnrolled, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -904,6 +938,11 @@
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
+ statsAction, statsClient);
+ }
+
/**
* Sends a debug message to the HAL with the provided FileDescriptor and arguments.
*/
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 1e0e799..8d76e9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -23,7 +23,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.FaceManager;
import android.os.IBinder;
@@ -32,6 +31,8 @@
import com.android.internal.R;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -41,6 +42,7 @@
import com.android.server.biometrics.sensors.face.UsageStats;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -61,16 +63,17 @@
private SensorPrivacyManager mSensorPrivacyManager;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
- boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
boolean isKeyguardBypassEnabled) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 8068e14..226e458 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.Status;
import android.hardware.face.Face;
@@ -32,6 +31,8 @@
import com.android.internal.R;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -40,6 +41,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.Supplier;
/**
* Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} HIDL
@@ -53,14 +55,14 @@
@NonNull private final int[] mEnrollIgnoreList;
@NonNull private final int[] mEnrollIgnoreListVendor;
- FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ FaceEnrollClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId) {
+ @Nullable Surface previewSurface, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
- false /* shouldVibrate */);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
mEnrollIgnoreList = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index e29a192..97838a7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -25,12 +25,15 @@
import android.util.Slog;
import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Supplier;
/**
* Face-specific generateChallenge client supporting the
@@ -48,10 +51,12 @@
private Long mChallengeResult;
FaceGenerateChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, long now) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, long now) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
mCreatedAt = now;
mWaiting = new ArrayList<>();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 0a9d96d..9812536 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.OptionalBool;
import android.hardware.biometrics.face.V1_0.Status;
@@ -28,10 +27,14 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
+import java.util.function.Supplier;
+
/**
* Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
* HIDL interface.
@@ -44,15 +47,15 @@
private final int mFaceId;
private boolean mValue;
- FaceGetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, int faceId) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, int faceId) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mFaceId = faceId;
-
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index 1e3b92d..d21a750 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -30,6 +31,7 @@
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Face-specific internal cleanup client supporting the
@@ -38,30 +40,33 @@
class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
FaceInternalCleanupClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList,
+ @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
- LazyDaemon<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) {
+ Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
+ List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
- LazyDaemon<IBiometricsFace> lazyDaemon, IBinder token,
+ Supplier<IBiometricsFace> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FaceRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, authenticatorIds);
+ sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index f2a9afc..250dd7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -18,17 +18,19 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import java.util.List;
+import java.util.function.Supplier;
/**
* Face-specific internal enumerate client supporting the
@@ -38,11 +40,12 @@
private static final String TAG = "FaceInternalEnumerateClient";
FaceInternalEnumerateClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId) {
+ @NonNull BiometricUtils<Face> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FACE);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index 3ae2011..0ee7a35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -18,18 +18,20 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.face.Face;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -40,12 +42,14 @@
private final int mBiometricId;
- FaceRemovalClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
- int sensorId, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FACE);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
+ biometricContext, authenticatorIds);
mBiometricId = biometricId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index ee01c43..6e74d36 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -18,16 +18,18 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -40,11 +42,11 @@
private final ArrayList<Byte> mHardwareAuthToken;
FaceResetLockoutClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = new ArrayList<>();
for (byte b : hardwareAuthToken) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index 5ec7a98..b7b0dc04 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -23,8 +23,12 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
+import java.util.function.Supplier;
+
/**
* Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
* HIDL interface.
@@ -34,9 +38,10 @@
private static final String TAG = "FaceRevokeChallengeClient";
FaceRevokeChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index ee28f7b..3c82f9c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.Status;
import android.os.IBinder;
@@ -26,11 +25,14 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.HalClientMonitor;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
@@ -45,13 +47,13 @@
private final ArrayList<Byte> mHardwareAuthToken;
private final int mFaceId;
- FaceSetFeatureClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId, int feature, boolean enabled,
- byte[] hardwareAuthToken, int faceId) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN);
+ logger, biometricContext);
mFeature = feature;
mEnabled = enabled;
mFaceId = faceId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8ee8ce5..8385c3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -18,18 +18,20 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.Environment;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import java.io.File;
import java.util.Map;
+import java.util.function.Supplier;
public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
private static final String TAG = "FaceUpdateActiveUserClient";
@@ -39,12 +41,12 @@
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
FaceUpdateActiveUserClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, boolean hasEnrolledBiometrics,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mHasEnrolledBiometrics = hasEnrolledBiometrics;
mAuthenticatorIds = authenticatorIds;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 6366e19..b4befd2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -83,6 +83,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -815,7 +816,8 @@
UserHandle.USER_CURRENT) != 0) {
fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+ BiometricContext.getInstance(getContext()));
} else {
fingerprint21 = Fingerprint21.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor, mHandler,
@@ -843,7 +845,8 @@
final FingerprintProvider provider =
new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
instance, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher);
+ mGestureAvailabilityDispatcher,
+ BiometricContext.getInstance(getContext()));
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
new file mode 100644
index 0000000..55861bb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.fingerprint.ISession;
+
+/**
+ * A holder for an AIDL {@link ISession} with additional metadata about the current user
+ * and the backend.
+ */
+public class AidlSession {
+
+ private final int mHalInterfaceVersion;
+ @NonNull private final ISession mSession;
+ private final int mUserId;
+ @NonNull private final HalSessionCallback mHalSessionCallback;
+
+ public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
+ HalSessionCallback halSessionCallback) {
+ mHalInterfaceVersion = halInterfaceVersion;
+ mSession = session;
+ mUserId = userId;
+ mHalSessionCallback = halSessionCallback;
+ }
+
+ /** The underlying {@link ISession}. */
+ @NonNull public ISession getSession() {
+ return mSession;
+ }
+
+ /** The user id associated with the session. */
+ public int getUserId() {
+ return mUserId;
+ }
+
+ /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
+ HalSessionCallback getHalSessionCallback() {
+ return mHalSessionCallback;
+ }
+
+ /**
+ * If this backend implements the *WithContext methods for enroll, authenticate, and
+ * detectInteraction. These variants should always be called if they are available.
+ */
+ public boolean hasContextMethods() {
+ return mHalInterfaceVersion >= 2;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index b29fbb6..0528cd4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -157,7 +157,7 @@
}
mEnrollmentIds.add(nextRandomId);
- mSensor.getSessionForUser(userId).mHalSessionCallback
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
.onEnrollmentProgress(nextRandomId, 0 /* remaining */);
}
@@ -173,7 +173,7 @@
return;
}
final int fid = fingerprints.get(0).getBiometricId();
- mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationSucceeded(fid,
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationSucceeded(fid,
HardwareAuthTokenUtils.toHardwareAuthToken(new byte[69]));
}
@@ -181,14 +181,14 @@
public void rejectAuthentication(int userId) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
- mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@Override
public void notifyAcquired(int userId, int acquireInfo) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
- mSensor.getSessionForUser(userId).mHalSessionCallback
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
.onAcquired((byte) acquireInfo, 0 /* vendorCode */);
}
@@ -196,7 +196,7 @@
public void notifyError(int userId, int errorCode) {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
- mSensor.getSessionForUser(userId).mHalSessionCallback.onError((byte) errorCode,
+ mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
0 /* vendorCode */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f3d0121..d26a780 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -23,9 +23,9 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -33,6 +33,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -47,12 +49,13 @@
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Fingerprint-specific authentication client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<ISession> implements
+class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements
Udfps, LockoutConsumer {
private static final String TAG = "FingerprintAuthenticationClient";
@@ -65,19 +68,22 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
- int sensorId, boolean isStrongBiometric, int statsClient,
+ int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ boolean isStrongBiometric,
@Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
- cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+ cookie, requireConfirmation, sensorId,
+ biometricLogger, biometricContext,
+ isStrongBiometric, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
@@ -102,7 +108,8 @@
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
+ return new ClientMonitorCompositeCallback(mALSProbeCallback,
+ getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -158,7 +165,7 @@
mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
try {
- mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ mCancellationSignal = doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
@@ -168,9 +175,31 @@
}
}
+ private ICancellationSignal doAuthenticate() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ final OperationContext opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().authenticateWithContext(
+ mOperationId, opContext);
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
+ } else {
+ return session.getSession().authenticate(mOperationId);
+ }
+ }
+
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
+ unsubscribeBiometricContext();
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
@@ -191,7 +220,20 @@
mIsPointerDown = true;
mState = STATE_STARTED;
mALSProbeCallback.getProbe().enable();
- getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+
+ final AidlSession session = getFreshDaemon();
+ if (session.hasContextMethods()) {
+ final PointerContext context = new PointerContext();
+ context.pointerId = 0;
+ context.x = x;
+ context.y = y;
+ context.minor = minor;
+ context.major = major;
+ context.isAoD = getBiometricContext().isAoD();
+ session.getSession().onPointerDownWithContext(context);
+ } else {
+ session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ }
if (getListener() != null) {
getListener().onUdfpsPointerDown(getSensorId());
@@ -207,7 +249,15 @@
mIsPointerDown = false;
mState = STATE_STARTED_PAUSED_ATTEMPTED;
mALSProbeCallback.getProbe().disable();
- getFreshDaemon().onPointerUp(0 /* pointerId */);
+
+ final AidlSession session = getFreshDaemon();
+ if (session.hasContextMethods()) {
+ final PointerContext context = new PointerContext();
+ context.pointerId = 0;
+ session.getSession().onPointerUpWithContext(context);
+ } else {
+ session.getSession().onPointerUp(0 /* pointerId */);
+ }
if (getListener() != null) {
getListener().onUdfpsPointerUp(getSensorId());
@@ -225,7 +275,7 @@
@Override
public void onUiReady() {
try {
- getFreshDaemon().onUiReady();
+ getFreshDaemon().getSession().onUiReady();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -237,8 +287,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -256,8 +306,8 @@
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), error, 0 /* vendorCode */,
- isCryptoOperation(), getTargetUserId());
+ getLogger().logOnError(getContext(), getOperationContext(),
+ error, 0 /* vendorCode */, getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 1f0482d..0e89814 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -20,26 +20,28 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.DetectionConsumer;
import com.android.server.biometrics.sensors.SensorOverlays;
+import java.util.function.Supplier;
+
/**
* Performs fingerprint detection without exposing any matching information (e.g. accept/reject
* have the same haptic, lockout counter is not increased).
*/
-class FingerprintDetectClient extends AcquisitionClient<ISession> implements DetectionConsumer {
+class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
private static final String TAG = "FingerprintDetectClient";
@@ -47,15 +49,14 @@
@NonNull private final SensorOverlays mSensorOverlays;
@Nullable private ICancellationSignal mCancellationSignal;
- FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
- int statsClient) {
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
@@ -84,7 +85,7 @@
mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
try {
- mCancellationSignal = getFreshDaemon().detectInteraction();
+ mCancellationSignal = doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
mSensorOverlays.hide(getSensorId());
@@ -92,6 +93,16 @@
}
}
+ private ICancellationSignal doDetectInteraction() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ return session.getSession().detectInteractionWithContext(getOperationContext());
+ } else {
+ return session.getSession().detectInteraction();
+ }
+ }
+
@Override
public void onInteractionDetected() {
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 169c3eb..e21d901 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -22,19 +22,24 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -46,12 +51,15 @@
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
+import java.util.function.Supplier;
+
+class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps {
private static final String TAG = "FingerprintEnrollClient";
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final SensorOverlays mSensorOverlays;
+ @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
private final @FingerprintManager.EnrollReason int mEnrollReason;
@Nullable private ICancellationSignal mCancellationSignal;
@@ -59,23 +67,26 @@
private boolean mIsPointerDown;
FingerprintEnrollClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
- !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
+ 0 /* timeoutSec */, sensorId,
+ !sensorProps.isAnyUdfpsType() /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
+ mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
getLogger().disableMetrics();
@@ -85,8 +96,8 @@
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().createALSCallback(true /* startWithClient */), callback);
+ return new ClientMonitorCompositeCallback(mALSProbeCallback,
+ getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -135,8 +146,46 @@
}
@Override
+ protected void startHalOperation() {
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+
+ BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
+ try {
+ mCancellationSignal = doEnroll();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting enroll", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ private ICancellationSignal doEnroll() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+ final HardwareAuthToken hat =
+ HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+ if (session.hasContextMethods()) {
+ final OperationContext opContext = getOperationContext();
+ final ICancellationSignal cancel = session.getSession().enrollWithContext(
+ hat, opContext);
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
+ return cancel;
+ } else {
+ return session.getSession().enroll(hat);
+ }
+ }
+
+ @Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
+ unsubscribeBiometricContext();
if (mCancellationSignal != null) {
try {
@@ -151,26 +200,24 @@
}
@Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
-
- BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
- try {
- mCancellationSignal = getFreshDaemon().enroll(
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
public void onPointerDown(int x, int y, float minor, float major) {
try {
mIsPointerDown = true;
- getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ mALSProbeCallback.getProbe().enable();
+
+ final AidlSession session = getFreshDaemon();
+ if (session.hasContextMethods()) {
+ final PointerContext context = new PointerContext();
+ context.pointerId = 0;
+ context.x = x;
+ context.y = y;
+ context.minor = minor;
+ context.major = major;
+ context.isAoD = getBiometricContext().isAoD();
+ session.getSession().onPointerDownWithContext(context);
+ } else {
+ session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer down", e);
}
@@ -180,7 +227,16 @@
public void onPointerUp() {
try {
mIsPointerDown = false;
- getFreshDaemon().onPointerUp(0 /* pointerId */);
+ mALSProbeCallback.getProbe().disable();
+
+ final AidlSession session = getFreshDaemon();
+ if (session.hasContextMethods()) {
+ final PointerContext context = new PointerContext();
+ context.pointerId = 0;
+ session.getSession().onPointerUpWithContext(context);
+ } else {
+ session.getSession().onPointerUp(0 /* pointerId */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send pointer up", e);
}
@@ -194,7 +250,7 @@
@Override
public void onUiReady() {
try {
- getFreshDaemon().onUiReady();
+ getFreshDaemon().getSession().onUiReady();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to send UI ready", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 4f54f8a..ddae8be 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -19,32 +19,37 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISession> {
+class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FingerprintGenerateChallengeClient";
FingerprintGenerateChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId,
+ biometricLogger, biometricContext);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().generateChallenge();
+ getFreshDaemon().getSession().generateChallenge();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to generateChallenge", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index 52bd234..ea1a622 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -18,29 +18,31 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import java.util.Map;
+import java.util.function.Supplier;
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> {
+class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
private static final String TAG = "FingerprintGetAuthenticatorIdClient";
private final Map<Integer, Long> mAuthenticatorIds;
FingerprintGetAuthenticatorIdClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, Map<Integer, Long> authenticatorIds) {
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
mAuthenticatorIds = authenticatorIds;
}
@@ -57,7 +59,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getAuthenticatorId();
+ getFreshDaemon().getSession().getAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 0de3f4f..09bdd6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -19,10 +19,11 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -31,36 +32,44 @@
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Fingerprint-specific internal cleanup client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, ISession> {
+class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
FingerprintInternalCleanupClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull List<Fingerprint> enrolledList,
+ @NonNull Supplier<AidlSession> lazyDaemon,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull List<Fingerprint> enrolledList,
@NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+ enrolledList, utils, authenticatorIds);
}
@Override
- protected InternalEnumerateClient<ISession> getEnumerateClient(Context context,
- LazyDaemon<ISession> lazyDaemon, IBinder token, int userId, String owner,
- List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) {
+ protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context,
+ Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner,
+ List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId,
+ logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE),
+ biometricContext);
}
@Override
- protected RemovalClient<Fingerprint, ISession> getRemovalClient(Context context,
- LazyDaemon<ISession> lazyDaemon, IBinder token, int biometricId, int userId,
+ protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context,
+ Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId,
String owner, BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
Map<Integer, Long> authenticatorIds) {
return new FingerprintRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner,
- utils, sensorId, authenticatorIds);
+ utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE),
+ biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
index e20544a..a5a832a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java
@@ -18,37 +18,39 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import java.util.List;
+import java.util.function.Supplier;
/**
* Fingerprint-specific internal client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<ISession> {
+class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> {
private static final String TAG = "FingerprintInternalEnumerateClient";
protected FingerprintInternalEnumerateClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, int userId,
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId,
@NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+ @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().enumerateEnrollments();
+ getFreshDaemon().getSession().enumerateEnrollments();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enumerate", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
index 6cd2ef1..bc02897 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java
@@ -19,28 +19,32 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.InvalidationClient;
import java.util.Map;
+import java.util.function.Supplier;
-public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, ISession> {
+public class FingerprintInvalidationClient extends InvalidationClient<Fingerprint, AidlSession> {
private static final String TAG = "FingerprintInvalidationClient";
public FingerprintInvalidationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, int sensorId,
+ @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) {
- super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback);
+ super(context, lazyDaemon, userId, sensorId, logger, biometricContext,
+ authenticatorIds, callback);
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().invalidateAuthenticatorId();
+ getFreshDaemon().getSession().invalidateAuthenticatorId();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index efc9304..f810bca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
@@ -53,6 +54,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -98,7 +101,7 @@
@NonNull private final BiometricTaskStackListener mTaskStackListener;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
@@ -141,7 +144,8 @@
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props, @NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
@@ -150,6 +154,7 @@
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
+ mBiometricContext = biometricContext;
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
@@ -181,7 +186,8 @@
location.sensorRadius))
.collect(Collectors.toList()));
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
+ internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);
@@ -298,6 +304,9 @@
new FingerprintGetAuthenticatorIdClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
@@ -307,6 +316,8 @@
mHandler.post(() -> {
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
+ BiometricLogger.ofUnknown(mContext),
+ mBiometricContext,
FingerprintUtils.getInstance(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -317,7 +328,10 @@
mHandler.post(() -> {
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, hardwareAuthToken,
mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
scheduleForSensor(sensorId, client);
});
@@ -331,7 +345,9 @@
new FingerprintGenerateChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- sensorId);
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
scheduleForSensor(sensorId, client);
});
}
@@ -343,7 +359,10 @@
final FingerprintRevokeChallengeClient client =
new FingerprintRevokeChallengeClient(mContext,
mSensors.get(sensorId).getLazySession(), token,
- userId, opPackageName, sensorId, challenge);
+ userId, opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
}
@@ -361,6 +380,9 @@
mSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -399,8 +421,10 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
- opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
+ opPackageName, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext,
+ mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -417,7 +441,9 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
userId, operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ false /* requireConfirmation */, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
@@ -479,6 +505,9 @@
mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -492,14 +521,22 @@
final FingerprintInternalCleanupClient client =
new FingerprintInternalCleanupClient(mContext,
mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FingerprintUtils.getInstance(sensorId),
+ mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
+ enrolledList, FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
mFingerprintStateCallback));
});
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ statsAction, statsClient);
+ }
+
@Override
public boolean isHardwareDetected(int sensorId) {
return hasHalInstance();
@@ -524,6 +561,9 @@
final FingerprintInvalidationClient client =
new FingerprintInvalidationClient(mContext,
mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index 9a9d6ab..d559bb1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -19,42 +19,44 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Fingerprint-specific removal client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
*/
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> {
+class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
private static final String TAG = "FingerprintRemovalClient";
private final int[] mBiometricIds;
FingerprintRemovalClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext, authenticatorIds);
mBiometricIds = biometricIds;
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().removeEnrollments(mBiometricIds);
+ getFreshDaemon().getSession().removeEnrollments(mBiometricIds);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting remove", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ee8d170..f90cba7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -18,15 +18,15 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -34,12 +34,14 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific resetLockout client for the {@link IFingerprint} AIDL HAL interface.
* Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
* cleared.
*/
-class FingerprintResetLockoutClient extends HalClientMonitor<ISession> implements ErrorConsumer {
+class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
private static final String TAG = "FingerprintResetLockoutClient";
@@ -48,12 +50,12 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
FingerprintResetLockoutClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, biometricLogger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -73,7 +75,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().resetLockout(mHardwareAuthToken);
+ getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index 9e6f1bc..afa62e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -19,33 +19,38 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<ISession> {
+class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
private static final String TAG = "FingerpirntRevokeChallengeClient";
private final long mChallenge;
FingerprintRevokeChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId, long challenge) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ long challenge) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
mChallenge = challenge;
}
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().revokeChallenge(mChallenge);
+ getFreshDaemon().getSession().revokeChallenge(mChallenge);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to revokeChallenge", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index 9f11df6..52305a3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -27,20 +27,25 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StartUserClient;
+import java.util.function.Supplier;
+
public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
private static final String TAG = "FingerprintStartUserClient";
@NonNull private final ISessionCallback mSessionCallback;
public FingerprintStartUserClient(@NonNull Context context,
- @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+ @NonNull Supplier<IFingerprint> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull ISessionCallback sessionCallback,
@NonNull UserStartedCallback<ISession> callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mSessionCallback = sessionCallback;
}
@@ -53,10 +58,12 @@
@Override
protected void startHalOperation() {
try {
- final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ final IFingerprint hal = getFreshDaemon();
+ final int version = hal.getInterfaceVersion();
+ final ISession newSession = hal.createSession(getSensorId(),
getTargetUserId(), mSessionCallback);
Binder.allowBlocking(newSession.asBinder());
- mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession, version);
getCallback().onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -66,6 +73,5 @@
@Override
public void unableToStart() {
-
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index 9d38145..2cc1879 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -19,21 +19,26 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.fingerprint.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.StopUserClient;
-public class FingerprintStopUserClient extends StopUserClient<ISession> {
+import java.util.function.Supplier;
+
+public class FingerprintStopUserClient extends StopUserClient<AidlSession> {
private static final String TAG = "FingerprintStopUserClient";
public FingerprintStopUserClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
@@ -45,7 +50,7 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().close();
+ getFreshDaemon().getSession().close();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
getCallback().onClientFinished(this, false /* success */);
@@ -54,6 +59,5 @@
@Override
public void unableToStart() {
-
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 59e4b58..63e345e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -39,19 +39,21 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -66,13 +68,14 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Maintains the state of a single sensor within an instance of the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL.
*/
@SuppressWarnings("deprecation")
-class Sensor {
+public class Sensor {
private boolean mTestHalEnabled;
@@ -86,26 +89,11 @@
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @Nullable private Session mCurrentSession;
- @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
+ @Nullable private AidlSession mCurrentSession;
+ @NonNull private final Supplier<AidlSession> mLazySession;
- static class Session {
- @NonNull private final String mTag;
- @NonNull private final ISession mSession;
- private final int mUserId;
- @NonNull final HalSessionCallback mHalSessionCallback;
-
- Session(@NonNull String tag, @NonNull ISession session, int userId,
- @NonNull HalSessionCallback halSessionCallback) {
- mTag = tag;
- mSession = session;
- mUserId = userId;
- mHalSessionCallback = halSessionCallback;
- Slog.d(mTag, "New session created for user: " + userId);
- }
- }
-
- static class HalSessionCallback extends ISessionCallback.Stub {
+ @VisibleForTesting
+ public static class HalSessionCallback extends ISessionCallback.Stub {
/**
* Interface to sends results to the HalSessionCallback's owner.
@@ -441,7 +429,8 @@
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -452,32 +441,35 @@
mScheduler = new UserAwareBiometricScheduler(tag,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher,
- () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@NonNull
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new FingerprintStopUserClient(mContext, mLazySession, mToken,
- userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+ userId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
+ () -> mCurrentSession = null);
}
@NonNull
@Override
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
-
final int sensorId = mSensorProperties.sensorId;
final HalSessionCallback resultController = new HalSessionCallback(mContext,
mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher, callback);
+ lockoutResetDispatcher, () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
- (userIdStarted, newSession) -> {
- mCurrentSession = new Session(mTag,
+ (userIdStarted, newSession, halInterfaceVersion) -> {
+ Slog.d(mTag, "New session created for user: "
+ + userIdStarted + " with hal version: "
+ + halInterfaceVersion);
+ mCurrentSession = new AidlSession(halInterfaceVersion,
newSession, userIdStarted, resultController);
if (FingerprintUtils.getInstance(sensorId)
.isInvalidationInProgress(mContext, userIdStarted)) {
@@ -493,14 +485,15 @@
return new FingerprintStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
+ BiometricLogger.ofUnknown(mContext), biometricContext,
resultController, userStartedCallback);
}
});
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
}
- @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
+ @NonNull Supplier<AidlSession> getLazySession() {
return mLazySession;
}
@@ -508,8 +501,8 @@
return mSensorProperties;
}
- @Nullable Session getSessionForUser(int userId) {
- if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
+ @Nullable AidlSession getSessionForUser(int userId) {
+ if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
return null;
@@ -539,10 +532,10 @@
if (enabled != mTestHalEnabled) {
// The framework should retrieve a new session from the HAL.
try {
- if (mCurrentSession != null && mCurrentSession.mSession != null) {
+ if (mCurrentSession != null) {
// TODO(181984005): This should be scheduled instead of directly invoked
Slog.d(mTag, "Closing old session");
- mCurrentSession.mSession.close();
+ mCurrentSession.getSession().close();
}
} catch (RemoteException e) {
Slog.e(mTag, "RemoteException", e);
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/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index f160dff..9d60859 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -57,6 +57,8 @@
import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
@@ -67,7 +69,6 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -90,6 +91,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/**
* Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -111,13 +113,14 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockoutFrameworkImpl mLockoutTracker;
private final BiometricTaskStackListener mTaskStackListener;
- private final HalClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
+ private final Supplier<IBiometricsFingerprint> mLazyDaemon;
private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFingerprint mDaemon;
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -318,15 +321,18 @@
}
}
+ @VisibleForTesting
Fingerprint21(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller) {
+ @NonNull HalResultController controller,
+ @NonNull BiometricContext biometricContext) {
mContext = context;
mFingerprintStateCallback = fingerprintStateCallback;
+ mBiometricContext = biometricContext;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -368,7 +374,7 @@
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler, scheduler);
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, BiometricContext.getInstance(context));
}
@Override
@@ -493,6 +499,9 @@
final FingerprintUpdateActiveUserClient client =
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -536,7 +545,10 @@
// thread.
mHandler.post(() -> {
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
- userId, mContext.getOpPackageName(), sensorId, mLockoutTracker);
+ userId, mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mLockoutTracker);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -548,7 +560,10 @@
final FingerprintGenerateChallengeClient client =
new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -559,7 +574,10 @@
mHandler.post(() -> {
final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
mContext, mLazyDaemon, token, userId, opPackageName,
- mSensorProperties.sensorId);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
}
@@ -577,7 +595,11 @@
mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
userId, hardwareAuthToken, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
- mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext,
+ mUdfpsOverlayController, mSidefpsController,
enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -616,8 +638,10 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, userId, opPackageName,
- mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, mUdfpsOverlayController,
+ isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
@@ -636,7 +660,9 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
restricted, opPackageName, cookie, false /* requireConfirmation */,
- mSensorProperties.sensorId, isStrongBiometric, statsClient,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication, mSensorProperties);
@@ -678,7 +704,10 @@
final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId, mAuthenticatorIds);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -695,7 +724,10 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId, mAuthenticatorIds);
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -709,7 +741,10 @@
mSensorProperties.sensorId, userId);
final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId, enrolledList,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN),
+ mBiometricContext, enrolledList,
FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
});
@@ -722,6 +757,11 @@
mFingerprintStateCallback));
}
+ private BiometricLogger createLogger(int statsAction, int statsClient) {
+ return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ statsAction, statsClient);
+ }
+
@Override
public boolean isHardwareDetected(int sensorId) {
return getDaemon() != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 1694bd9..149526f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -37,6 +37,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -247,7 +248,8 @@
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
Slog.d(TAG, "Creating Fingerprint23Mock!");
final Handler handler = new Handler(Looper.getMainLooper());
@@ -256,7 +258,7 @@
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
- handler, lockoutResetDispatcher, controller);
+ handler, lockoutResetDispatcher, controller, biometricContext);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -385,9 +387,10 @@
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull MockHalResultController controller) {
+ @NonNull MockHalResultController controller,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, biometricContext);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 87d47c1..97fbb5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -23,7 +23,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -32,6 +31,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.AuthenticationClient;
@@ -45,6 +46,7 @@
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Fingerprint-specific authentication client supporting the
@@ -64,11 +66,12 @@
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
- int sensorId, boolean isStrongBiometric, int statsClient,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -76,10 +79,9 @@
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
- lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
- false /* isKeyguardBypassEnabled */);
+ owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
+ true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 9137212..c2929d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -30,6 +29,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -40,6 +41,7 @@
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* Performs fingerprint detection without exposing any matching information (e.g. accept/reject
@@ -55,14 +57,14 @@
private boolean mIsPointerDown;
public FingerprintDetectClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
- boolean isStrongBiometric, int statsClient) {
+ int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mIsStrongBiometric = isStrongBiometric;
@@ -128,8 +130,9 @@
@Override
public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */,
- isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */);
+ getLogger().logOnAuthenticated(getContext(), getOperationContext(),
+ authenticated, false /* requireConfirmation */,
+ getTargetUserId(), false /* isBiometricPrompt */);
// Do not distinguish between success/failures.
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 82b046d..1d478e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -31,6 +30,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -41,6 +42,8 @@
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific enroll client supporting the
* {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -56,16 +59,17 @@
private boolean mIsPointerDown;
FingerprintEnrollClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
+ @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
@FingerprintManager.EnrollReason int enrollReason) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
- true /* shouldVibrate */);
+ timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
+ biometricContext);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index db2f045..3bb7135 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -23,9 +23,13 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific generateChallenge/preEnroll client supporting the
* {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -37,10 +41,12 @@
private static final String TAG = "FingerprintGenerateChallengeClient";
FingerprintGenerateChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId);
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
+ biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index a42a8ae..5e7cf35 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -30,6 +31,7 @@
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Fingerprint-specific internal cleanup client supporting the
@@ -40,32 +42,36 @@
extends InternalCleanupClient<Fingerprint, IBiometricsFingerprint> {
FingerprintInternalCleanupClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull List<Fingerprint> enrolledList,
@NonNull BiometricUtils<Fingerprint> utils,
@NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds);
+ super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
+ enrolledList, utils, authenticatorIds);
}
@Override
protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
- Context context, LazyDaemon<IBiometricsFingerprint> lazyDaemon, IBinder token,
+ Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
int userId, String owner, List<Fingerprint> enrolledList,
- BiometricUtils<Fingerprint> utils, int sensorId) {
+ BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId);
+ enrolledList, utils, sensorId, logger, biometricContext);
}
@Override
protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
- LazyDaemon<IBiometricsFingerprint> lazyDaemon, IBinder token,
+ Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
- int sensorId, Map<Integer, Long> authenticatorIds) {
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
// Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
// is all done internally.
return new FingerprintRemovalClient(context, lazyDaemon, token,
null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, authenticatorIds);
+ sensorId, logger, biometricContext, authenticatorIds);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index 7117cf3..0840f1b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -18,17 +18,19 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
import java.util.List;
+import java.util.function.Supplier;
/**
* Fingerprint-specific internal enumerate client supporting the
@@ -39,11 +41,12 @@
private static final String TAG = "FingerprintInternalEnumerateClient";
FingerprintInternalEnumerateClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId) {
+ @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 2f360f3..9ec56c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -18,18 +18,20 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.RemovalClient;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Fingerprint-specific removal client supporting the
@@ -42,12 +44,13 @@
private final int mBiometricId;
FingerprintRemovalClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@NonNull Map<Integer, Long> authenticatorIds) {
super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ logger, biometricContext, authenticatorIds);
mBiometricId = biometricId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index ed28e3f..559ca06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -18,9 +18,10 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -33,10 +34,11 @@
@NonNull final LockoutFrameworkImpl mLockoutTracker;
public FingerprintResetLockoutClient(@NonNull Context context, int userId,
- @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) {
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull LockoutFrameworkImpl lockoutTracker) {
super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
- sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ sensorId, logger, biometricContext);
mLockoutTracker = lockoutTracker;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index b6b29b3..6273417 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -23,8 +23,12 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.RevokeChallengeClient;
+import java.util.function.Supplier;
+
/**
* Fingerprint-specific revokeChallenge client supporting the
* {@link android.hardware.biometrics.fingerprint.V2_1} and
@@ -36,9 +40,10 @@
private static final String TAG = "FingerprintRevokeChallengeClient";
FingerprintRevokeChallengeClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId) {
- super(context, lazyDaemon, token, userId, owner, sensorId);
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ int userId, @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index d317984..a4e6025 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.os.Build;
import android.os.Environment;
@@ -27,6 +26,8 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -49,13 +50,14 @@
private File mDirectory;
FingerprintUpdateActiveUserClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId, Supplier<Integer> currentUserId,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ Supplier<Integer> currentUserId,
boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
boolean forceUpdateAuthenticatorId) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ 0 /* cookie */, sensorId, logger, biometricContext);
mCurrentUserId = currentUserId;
mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
mHasEnrolledBiometrics = hasEnrolledBiometrics;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1e00ea9..1b0341c 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -308,7 +308,8 @@
public void onFixedRotationFinished(int displayId) { }
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) { }
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 5b76695..53fe450 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);
}
}
@@ -1090,6 +1090,10 @@
&& mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
return;
}
+ if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
+ callingPackage) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
// Don't notify if already notified for this uid and clip.
if (clipboard.mNotifiedUids.get(uid)) {
return;
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index a5024ff..603f206 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -24,7 +24,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -94,6 +93,7 @@
private static String TAG = MultipathPolicyTracker.class.getSimpleName();
private static final boolean DBG = false;
+ private static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB
// This context is for the current user.
private final Context mContext;
@@ -278,15 +278,11 @@
}
private NetworkIdentity getTemplateMatchingNetworkIdentity(NetworkCapabilities nc) {
- return new NetworkIdentity(
- ConnectivityManager.TYPE_MOBILE,
- 0 /* subType, unused for template matching */,
- subscriberId,
- null /* networkId, unused for matching mobile networks */,
- !nc.hasCapability(NET_CAPABILITY_NOT_ROAMING),
- !nc.hasCapability(NET_CAPABILITY_NOT_METERED),
- false /* defaultNetwork, templates should have DEFAULT_NETWORK_ALL */,
- OEM_MANAGED_ALL);
+ return new NetworkIdentity.Builder().setType(ConnectivityManager.TYPE_MOBILE)
+ .setSubscriberId(subscriberId)
+ .setRoaming(!nc.hasCapability(NET_CAPABILITY_NOT_ROAMING))
+ .setMetered(!nc.hasCapability(NET_CAPABILITY_NOT_METERED))
+ .build();
}
private long getRemainingDailyBudget(long limitBytes,
@@ -375,7 +371,7 @@
// This will only be called if the total quota for the day changed, not if usage changed
// since last time, so even if this is called very often the budget will not snap to 0
// as soon as there are less than 2MB left for today.
- if (budget > NetworkStatsManager.MIN_THRESHOLD_BYTES) {
+ if (budget > MIN_THRESHOLD_BYTES) {
if (DBG) {
Log.d(TAG, "Setting callback for " + budget + " bytes on network " + network);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c2ca3a5..d0e39cc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -23,7 +23,6 @@
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -348,7 +347,7 @@
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
- mOverrideRequestController.cancelOverrideRequests();
+ mOverrideRequestController.cancelOverrideRequest();
}
mOverrideRequestController.handleBaseStateChanged();
updatePendingStateLocked();
@@ -503,7 +502,7 @@
@OverrideRequestController.RequestStatus int status) {
if (status == STATUS_ACTIVE) {
mActiveOverride = Optional.of(request);
- } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
+ } else if (status == STATUS_CANCELED) {
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
}
@@ -528,8 +527,6 @@
// Schedule the notification now.
processRecord.notifyRequestActiveAsync(request.getToken());
}
- } else if (status == STATUS_SUSPENDED) {
- processRecord.notifyRequestSuspendedAsync(request.getToken());
} else {
processRecord.notifyRequestCanceledAsync(request.getToken());
}
@@ -595,15 +592,14 @@
}
}
- private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
+ private void cancelStateRequestInternal(int callingPid) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
-
- mOverrideRequestController.cancelRequest(token);
+ mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
}
}
@@ -628,6 +624,23 @@
}
}
+ /**
+ * Allow top processes to request or cancel a device state change. If the calling process ID is
+ * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+ * @param callingPid
+ */
+ private void checkCanControlDeviceState(int callingPid) {
+ // Allow top processes to request a device state change
+ // If the calling process ID is not the top app, then we check if this process
+ // holds a permission to CONTROL_DEVICE_STATE
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ if (topApp == null || topApp.getPid() != callingPid) {
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to request device state, "
+ + "or the call must come from the top focused app.");
+ }
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
@@ -716,24 +729,6 @@
});
}
- public void notifyRequestSuspendedAsync(IBinder token) {
- @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
- if (lastStatus != null
- && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
- return;
- }
-
- mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
- mHandler.post(() -> {
- try {
- mCallback.onRequestSuspended(token);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
- ex);
- }
- });
- }
-
public void notifyRequestCanceledAsync(IBinder token) {
@RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null && lastStatus == STATUS_CANCELED) {
@@ -782,12 +777,7 @@
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp.getPid() != callingPid) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to request device state, "
- + "or the call must come from the top focused app.");
- }
+ checkCanControlDeviceState(callingPid);
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
@@ -802,25 +792,16 @@
}
@Override // Binder call
- public void cancelRequest(IBinder token) {
+ public void cancelStateRequest() {
final int callingPid = Binder.getCallingPid();
// Allow top processes to cancel a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp.getPid() != callingPid) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to cancel device state, "
- + "or the call must come from the top focused app.");
- }
-
- if (token == null) {
- throw new IllegalArgumentException("Request token must not be null.");
- }
+ checkCanControlDeviceState(callingPid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- cancelRequestInternal(callingPid, token);
+ cancelStateRequestInternal(callingPid);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index eed68f8..659ee75 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -97,7 +97,7 @@
try {
if ("reset".equals(nextArg)) {
if (sLastRequest != null) {
- mClient.cancelRequest(sLastRequest);
+ mClient.cancelStateRequest();
sLastRequest = null;
}
} else {
@@ -105,9 +105,6 @@
DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
mClient.requestState(request, null /* executor */, null /* callback */);
- if (sLastRequest != null) {
- mClient.cancelRequest(sLastRequest);
- }
sLastRequest = request;
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 36cb416..01f5a09 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,15 +18,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
+import android.util.Slog;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
/**
* Manages the lifecycle of override requests.
@@ -36,29 +34,26 @@
* <ul>
* <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
* request will become suspended.</li>
- * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
+ * <li>The request is cancelled with {@link #cancelRequest} or as a side effect
* of other methods calls, such as {@link #handleProcessDied(int)}.</li>
* </ul>
*/
final class OverrideRequestController {
+ private static final String TAG = "OverrideRequestController";
+
static final int STATUS_UNKNOWN = 0;
/**
* The request is the top-most request.
*/
static final int STATUS_ACTIVE = 1;
/**
- * The request is still present but is being superseded by another request.
- */
- static final int STATUS_SUSPENDED = 2;
- /**
* The request is not longer valid.
*/
- static final int STATUS_CANCELED = 3;
+ static final int STATUS_CANCELED = 2;
@IntDef(prefix = {"STATUS_"}, value = {
STATUS_UNKNOWN,
STATUS_ACTIVE,
- STATUS_SUSPENDED,
STATUS_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
@@ -68,8 +63,6 @@
switch (status) {
case STATUS_ACTIVE:
return "ACTIVE";
- case STATUS_SUSPENDED:
- return "SUSPENDED";
case STATUS_CANCELED:
return "CANCELED";
case STATUS_UNKNOWN:
@@ -79,15 +72,13 @@
}
private final StatusChangeListener mListener;
- private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();
- // List of override requests with the most recent override request at the end.
- private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
+ // Handle to the current override request, null if none.
+ private OverrideRequest mRequest;
private boolean mStickyRequestsAllowed;
- // List of override requests that have outlived their process and will only be cancelled through
- // a call to cancelStickyRequests().
- private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
+ // The current request has outlived their process.
+ private boolean mStickyRequest;
OverrideRequestController(@NonNull StatusChangeListener listener) {
mListener = listener;
@@ -97,26 +88,26 @@
* Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
* to {@link #handleProcessDied(int)} will not result in the request being cancelled
* immediately. Instead, the request will be marked sticky and must be cancelled with a call
- * to {@link #cancelStickyRequests()}.
+ * to {@link #cancelStickyRequest()}.
*/
void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
mStickyRequestsAllowed = stickyRequestsAllowed;
if (!mStickyRequestsAllowed) {
- cancelStickyRequests();
+ cancelStickyRequest();
}
}
/**
- * Adds a request to the top of the stack and notifies the listener of all changes to request
- * status as a result of this operation.
+ * Sets the new request as active and cancels the previous override request, notifies the
+ * listener of all changes to request status as a result of this operation.
*/
void addRequest(@NonNull OverrideRequest request) {
- mRequests.add(request);
+ OverrideRequest previousRequest = mRequest;
+ mRequest = request;
mListener.onStatusChanged(request, STATUS_ACTIVE);
- if (mRequests.size() > 1) {
- OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
- mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
+ if (previousRequest != null) {
+ cancelRequestLocked(previousRequest);
}
}
@@ -124,42 +115,32 @@
* Cancels the request with the specified {@code token} and notifies the listener of all changes
* to request status as a result of this operation.
*/
- void cancelRequest(@NonNull IBinder token) {
- int index = getRequestIndex(token);
- if (index == -1) {
+ void cancelRequest(@NonNull OverrideRequest request) {
+ // Either don't have a current request or attempting to cancel an already cancelled request
+ if (!hasRequest(request.getToken())) {
return;
}
-
- OverrideRequest request = mRequests.remove(index);
- if (index == mRequests.size() && mRequests.size() > 0) {
- // We removed the current active request so we need to set the new active request
- // before cancelling this request.
- OverrideRequest newTop = getLast(mRequests);
- mListener.onStatusChanged(newTop, STATUS_ACTIVE);
- }
- mListener.onStatusChanged(request, STATUS_CANCELED);
+ cancelCurrentRequestLocked();
}
/**
- * Cancels all requests that are currently marked sticky and notifies the listener of all
+ * Cancels a request that is currently marked sticky and notifies the listener of all
* changes to request status as a result of this operation.
*
* @see #setStickyRequestsAllowed(boolean)
*/
- void cancelStickyRequests() {
- mTmpRequestsToCancel.clear();
- mTmpRequestsToCancel.addAll(mStickyRequests);
- cancelRequestsLocked(mTmpRequestsToCancel);
+ void cancelStickyRequest() {
+ if (mStickyRequest) {
+ cancelCurrentRequestLocked();
+ }
}
/**
- * Cancels all override requests, this could be due to the device being put
+ * Cancels the current override request, this could be due to the device being put
* into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS"
*/
- void cancelOverrideRequests() {
- mTmpRequestsToCancel.clear();
- mTmpRequestsToCancel.addAll(mRequests);
- cancelRequestsLocked(mTmpRequestsToCancel);
+ void cancelOverrideRequest() {
+ cancelCurrentRequestLocked();
}
/**
@@ -167,7 +148,7 @@
* {@code token}, {@code false} otherwise.
*/
boolean hasRequest(@NonNull IBinder token) {
- return getRequestIndex(token) != -1;
+ return mRequest != null && token == mRequest.getToken();
}
/**
@@ -176,139 +157,79 @@
* operation.
*/
void handleProcessDied(int pid) {
- if (mRequests.isEmpty()) {
+ if (mRequest == null) {
return;
}
- mTmpRequestsToCancel.clear();
- OverrideRequest prevActiveRequest = getLast(mRequests);
- for (OverrideRequest request : mRequests) {
- if (request.getPid() == pid) {
- mTmpRequestsToCancel.add(request);
+ if (mRequest.getPid() == pid) {
+ if (mStickyRequestsAllowed) {
+ // Do not cancel the requests now because sticky requests are allowed. These
+ // requests will be cancelled on a call to cancelStickyRequests().
+ mStickyRequest = true;
+ return;
}
+ cancelCurrentRequestLocked();
}
-
- if (mStickyRequestsAllowed) {
- // Do not cancel the requests now because sticky requests are allowed. These
- // requests will be cancelled on a call to cancelStickyRequests().
- mStickyRequests.addAll(mTmpRequestsToCancel);
- return;
- }
-
- cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
* Notifies the controller that the base state has changed. The controller will notify the
* listener of all changes to request status as a result of this change.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
*/
- boolean handleBaseStateChanged() {
- if (mRequests.isEmpty()) {
- return false;
+ void handleBaseStateChanged() {
+ if (mRequest == null) {
+ return;
}
- mTmpRequestsToCancel.clear();
- OverrideRequest prevActiveRequest = getLast(mRequests);
- for (int i = 0; i < mRequests.size(); i++) {
- OverrideRequest request = mRequests.get(i);
- if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
- mTmpRequestsToCancel.add(request);
- }
+ if ((mRequest.getFlags()
+ & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
+ cancelCurrentRequestLocked();
}
-
- final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
- return newActiveRequest;
}
/**
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
*/
- boolean handleNewSupportedStates(int[] newSupportedStates) {
- if (mRequests.isEmpty()) {
- return false;
+ void handleNewSupportedStates(int[] newSupportedStates) {
+ if (mRequest == null) {
+ return;
}
- mTmpRequestsToCancel.clear();
- for (int i = 0; i < mRequests.size(); i++) {
- OverrideRequest request = mRequests.get(i);
- if (!contains(newSupportedStates, request.getRequestedState())) {
- mTmpRequestsToCancel.add(request);
- }
+ if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+ cancelCurrentRequestLocked();
}
-
- final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
- return newActiveRequest;
}
void dumpInternal(PrintWriter pw) {
- final int requestCount = mRequests.size();
+ OverrideRequest overrideRequest = mRequest;
+ final boolean requestActive = overrideRequest != null;
pw.println();
- pw.println("Override requests: size=" + requestCount);
- for (int i = 0; i < requestCount; i++) {
- OverrideRequest overrideRequest = mRequests.get(i);
- int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
- pw.println(" " + i + ": mPid=" + overrideRequest.getPid()
+ pw.println("Override Request active: " + requestActive);
+ if (requestActive) {
+ pw.println("Request: mPid=" + overrideRequest.getPid()
+ ", mRequestedState=" + overrideRequest.getRequestedState()
+ ", mFlags=" + overrideRequest.getFlags()
- + ", mStatus=" + statusToString(status));
+ + ", mStatus=" + statusToString(STATUS_ACTIVE));
}
}
+ private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
+ mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+ }
+
/**
- * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
- * request becoming active this request will also be notified of its change in state.
- *
- * @return {@code true} if calling this method has lead to a new active request, {@code false}
- * otherwise.
+ * Handles cancelling {@code mRequest}.
+ * Notifies the listener of the canceled status as well.
*/
- private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
- if (requestsToCancel.isEmpty()) {
- return false;
+ private void cancelCurrentRequestLocked() {
+ if (mRequest == null) {
+ Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+ return;
}
-
- OverrideRequest prevActiveRequest = getLast(mRequests);
- boolean causedNewRequestToBecomeActive = false;
- mRequests.removeAll(requestsToCancel);
- mStickyRequests.removeAll(requestsToCancel);
- if (!mRequests.isEmpty()) {
- OverrideRequest newActiveRequest = getLast(mRequests);
- if (newActiveRequest != prevActiveRequest) {
- mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
- causedNewRequestToBecomeActive = true;
- }
- }
-
- for (int i = 0; i < requestsToCancel.size(); i++) {
- mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
- }
- return causedNewRequestToBecomeActive;
- }
-
- private int getRequestIndex(@NonNull IBinder token) {
- final int numberOfRequests = mRequests.size();
- if (numberOfRequests == 0) {
- return -1;
- }
-
- for (int i = 0; i < numberOfRequests; i++) {
- OverrideRequest request = mRequests.get(i);
- if (request.getToken() == token) {
- return i;
- }
- }
- return -1;
- }
-
- @Nullable
- private static <T> T getLast(List<T> list) {
- return list.size() > 0 ? list.get(list.size() - 1) : null;
+ mStickyRequest = false;
+ mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+ mRequest = null;
}
private static boolean contains(int[] array, int value) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index f4c36c6..162eb0e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -201,6 +201,10 @@
// Controls High Brightness Mode.
private HighBrightnessModeController mHbmController;
+ // Throttles (caps) maximum allowed brightness
+ private BrightnessThrottler mBrightnessThrottler;
+ private boolean mIsBrightnessThrottled;
+
// Context-sensitive brightness configurations require keeping track of the foreground app's
// package name and category, which is done by registering a TaskStackListener to call back to
// us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's
@@ -226,7 +230,7 @@
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, Context context,
- HighBrightnessModeController hbmController,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
@@ -235,8 +239,8 @@
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
ambientBrightnessThresholds, screenBrightnessThresholds, context,
- hbmController, idleModeBrightnessMapper, ambientLightHorizonShort,
- ambientLightHorizonLong
+ hbmController, brightnessThrottler, idleModeBrightnessMapper,
+ ambientLightHorizonShort, ambientLightHorizonLong
);
}
@@ -249,7 +253,7 @@
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, Context context,
- HighBrightnessModeController hbmController,
+ HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
int ambientLightHorizonLong) {
mInjector = injector;
@@ -291,6 +295,7 @@
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mHbmController = hbmController;
+ mBrightnessThrottler = brightnessThrottler;
mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
// Initialize to active (normal) screen brightness mode
@@ -365,6 +370,13 @@
prepareBrightnessAdjustmentSample();
}
changed |= setLightSensorEnabled(enable && !dozing);
+
+ if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) {
+ // Maximum brightness has changed, so recalculate display brightness.
+ mIsBrightnessThrottled = mBrightnessThrottler.isThrottled();
+ changed = true;
+ }
+
if (changed) {
updateAutoBrightness(false /*sendUpdate*/, userInitiatedChange);
}
@@ -412,7 +424,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)) {
@@ -855,8 +867,11 @@
// Clamps values with float range [0.0-1.0]
private float clampScreenBrightness(float value) {
- return MathUtils.constrain(value,
- mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+ final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ mBrightnessThrottler.getBrightnessCap());
+ final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ mBrightnessThrottler.getBrightnessCap());
+ return MathUtils.constrain(value, minBrightness, maxBrightness);
}
private void prepareBrightnessAdjustmentSample() {
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index a1d722b..c46ae85 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -69,8 +69,10 @@
*/
@Nullable
public static BrightnessMappingStrategy create(Resources resources,
- DisplayDeviceConfig displayDeviceConfig) {
- return create(resources, displayDeviceConfig, /* isForIdleMode= */ false, null);
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return create(resources, displayDeviceConfig, /* isForIdleMode= */ false,
+ displayWhiteBalanceController);
}
/**
@@ -845,7 +847,7 @@
float nits = mBrightnessSpline.interpolate(lux);
// Adjust nits to compensate for display white balance colour strength.
- if (mDisplayWhiteBalanceController != null && isForIdleMode()) {
+ if (mDisplayWhiteBalanceController != null) {
nits = mDisplayWhiteBalanceController.calculateAdjustedBrightnessNits(nits);
}
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
new file mode 100644
index 0000000..767b2d1
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.util.Slog;
+
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+
+import java.io.PrintWriter;
+
+/**
+ * This class monitors various conditions, such as skin temperature throttling status, and limits
+ * the allowed brightness range accordingly.
+ */
+class BrightnessThrottler {
+ private static final String TAG = "BrightnessThrottler";
+ private static final boolean DEBUG = false;
+
+ private static final int THROTTLING_INVALID = -1;
+
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private BrightnessThrottlingData mThrottlingData;
+ private final Runnable mThrottlingChangeCallback;
+ private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+ private int mThrottlingStatus;
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
+ Runnable throttlingChangeCallback) {
+ this(new Injector(), handler, throttlingData, throttlingChangeCallback);
+ }
+
+ BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData,
+ Runnable throttlingChangeCallback) {
+ mInjector = injector;
+ mHandler = handler;
+ mThrottlingData = throttlingData;
+ mThrottlingChangeCallback = throttlingChangeCallback;
+ mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
+
+ resetThrottlingData(mThrottlingData);
+ }
+
+ boolean deviceSupportsThrottling() {
+ return mThrottlingData != null;
+ }
+
+ float getBrightnessCap() {
+ return mBrightnessCap;
+ }
+
+ int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
+ boolean isThrottled() {
+ return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ }
+
+ void stop() {
+ mSkinThermalStatusObserver.stopObserving();
+
+ // We're asked to stop throttling, so reset brightness restrictions.
+ mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ // We set throttling status to an invalid value here so that we act on the first throttling
+ // value received from the thermal service after registration, even if that throttling value
+ // is THROTTLING_NONE.
+ mThrottlingStatus = THROTTLING_INVALID;
+ }
+
+ void resetThrottlingData(BrightnessThrottlingData throttlingData) {
+ stop();
+ mThrottlingData = throttlingData;
+
+ if (deviceSupportsThrottling()) {
+ mSkinThermalStatusObserver.startObserving();
+ }
+ }
+
+ private float verifyAndConstrainBrightnessCap(float brightness) {
+ if (brightness < PowerManager.BRIGHTNESS_MIN) {
+ Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible "
+ + "brightness " + PowerManager.BRIGHTNESS_MIN);
+ brightness = PowerManager.BRIGHTNESS_MIN;
+ }
+
+ if (brightness > PowerManager.BRIGHTNESS_MAX) {
+ Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible "
+ + "brightness " + PowerManager.BRIGHTNESS_MAX);
+ brightness = PowerManager.BRIGHTNESS_MAX;
+ }
+
+ return brightness;
+ }
+
+ private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
+ if (mThrottlingStatus != newStatus) {
+ mThrottlingStatus = newStatus;
+ updateThrottling();
+ }
+ }
+
+ private void updateThrottling() {
+ if (!deviceSupportsThrottling()) {
+ return;
+ }
+
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ if (mThrottlingStatus != THROTTLING_INVALID) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
+ if (level.thermalStatus <= mThrottlingStatus) {
+ brightnessCap = level.brightness;
+ brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+
+ if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) {
+ mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap);
+ mBrightnessMaxReason = brightnessMaxReason;
+
+ if (DEBUG) {
+ Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap
+ + ", mBrightnessMaxReason = "
+ + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+ }
+
+ if (mThrottlingChangeCallback != null) {
+ mThrottlingChangeCallback.run();
+ }
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
+ }
+
+ private void dumpLocal(PrintWriter pw) {
+ pw.println("BrightnessThrottler:");
+ pw.println(" mThrottlingData=" + mThrottlingData);
+ pw.println(" mThrottlingStatus=" + mThrottlingStatus);
+ pw.println(" mBrightnessCap=" + mBrightnessCap);
+ pw.println(" mBrightnessMaxReason=" +
+ BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+
+ mSkinThermalStatusObserver.dump(pw);
+ }
+
+ private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+ private final Injector mInjector;
+ private final Handler mHandler;
+
+ private IThermalService mThermalService;
+ private boolean mStarted;
+
+ SkinThermalStatusObserver(Injector injector, Handler handler) {
+ mInjector = injector;
+ mHandler = handler;
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ if (DEBUG) {
+ Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
+ }
+ mHandler.post(() -> {
+ final @Temperature.ThrottlingStatus int status = temp.getStatus();
+ thermalStatusChanged(status);
+ });
+ }
+
+ void startObserving() {
+ if (mStarted) {
+ if (DEBUG) {
+ Slog.d(TAG, "Thermal status observer already started");
+ }
+ return;
+ }
+ mThermalService = mInjector.getThermalService();
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ void stopObserving() {
+ if (!mStarted) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop skipped because thermal status observer not started");
+ }
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(this);
+ mStarted = false;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mThermalService = null;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println(" SkinThermalStatusObserver:");
+ writer.println(" mStarted: " + mStarted);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
+ }
+ }
+
+ public static class Injector {
+ public IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java
new file mode 100644
index 0000000..84fa0cc
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessUtils.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 com.android.server.display;
+
+import android.util.MathUtils;
+
+/**
+ * Utility class providing functions to convert between linear and perceptual gamma space.
+ *
+ * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is a
+ * slight improvement to the typical gamma transfer function for displays whose max brightness
+ * exceeds the 120 nit reference point, but doesn't set a specific reference brightness like the PQ
+ * function does.
+ *
+ * Note that this transfer function is only valid if the display's backlight value is a linear
+ * control. If it's calibrated to be something non-linear, then a different transfer function
+ * should be used.
+ *
+ * Note: This code is based on the same class in the com.android.settingslib.display package.
+ */
+public class BrightnessUtils {
+
+ // Hybrid Log Gamma constant values
+ private static final float R = 0.5f;
+ private static final float A = 0.17883277f;
+ private static final float B = 0.28466892f;
+ private static final float C = 0.55991073f;
+
+ /**
+ * A function for converting from the gamma space into the linear space.
+ *
+ * @param val The value in the gamma space [0 .. 1.0]
+ * @return The corresponding value in the linear space [0 .. 1.0].
+ */
+ public static final float convertGammaToLinear(float val) {
+ final float ret;
+ if (val <= R) {
+ ret = MathUtils.sq(val / R);
+ } else {
+ ret = MathUtils.exp((val - C) / A) + B;
+ }
+
+ // HLG is normalized to the range [0, 12], ensure that value is within that range,
+ // it shouldn't be out of bounds.
+ final float normalizedRet = MathUtils.constrain(ret, 0, 12);
+
+ // Re-normalize to the range [0, 1]
+ // in order to derive the correct setting value.
+ return normalizedRet / 12;
+ }
+
+ /**
+ * A function for converting from the linear space into the gamma space.
+ *
+ * @param val The value in linear space [0 .. 1.0]
+ * @return The corresponding value in gamma space [0 .. 1.0]
+ */
+ public static final float convertLinearToGamma(float val) {
+ // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1]
+ final float normalizedVal = val * 12;
+ final float ret;
+ if (normalizedVal <= 1f) {
+ ret = MathUtils.sqrt(normalizedVal) * R;
+ } else {
+ ret = A * MathUtils.log(normalizedVal - B) + C;
+ }
+ return ret;
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d0ce9ef..5de162c 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -222,6 +222,22 @@
}
/**
+ * Returns the system preferred display mode.
+ */
+ public Display.Mode getSystemPreferredDisplayModeLocked() {
+ return EMPTY_DISPLAY_MODE;
+ }
+
+ /**
+ * Returns the display mode that was being used when this display was first found by
+ * display manager.
+ * @hide
+ */
+ public Display.Mode getActiveDisplayModeAtStartLocked() {
+ return EMPTY_DISPLAY_MODE;
+ }
+
+ /**
* Sets the requested color mode.
*/
public void setRequestedColorModeLocked(int colorMode) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f3969b1..6866137 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -30,19 +30,27 @@
import android.util.Spline;
import android.view.DisplayAddress;
+import com.android.internal.annotations.VisibleForTesting;
+
import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.BrightnessThresholds;
+import com.android.server.display.config.BrightnessThrottlingMap;
+import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.Interpolation;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateRange;
+import com.android.server.display.config.SdrHdrRatioMap;
+import com.android.server.display.config.SdrHdrRatioPoint;
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.config.ThermalThrottling;
import com.android.server.display.config.Thresholds;
import com.android.server.display.config.XmlParser;
@@ -63,10 +71,126 @@
import javax.xml.datatype.DatatypeConfigurationException;
/**
- * Reads and stores display-specific configurations.
+ * Reads and stores display-specific configurations.
+ * File format:
+ * <pre>
+ * {@code
+ * <displayConfiguration>
+ * <densityMap>
+ * <density>
+ * <height>480</height>
+ * <width>720</width>
+ * <density>120</density>
+ * </density>
+ * <density>
+ * <height>720</height>
+ * <width>1280</width>
+ * <density>213</density>
+ * </density>
+ * <density>
+ * <height>1080</height>
+ * <width>1920</width>
+ * <density>320</density>
+ * </density>
+ * <density>
+ * <height>2160</height>
+ * <width>3840</width>
+ * <density>640</density>
+ * </density>
+ * </densityMap>
+ *
+ * <screenBrightnessMap>
+ * <point>
+ * <value>0.0</value>
+ * <nits>2.0</nits>
+ * </point>
+ * <point>
+ * <value>0.62</value>
+ * <nits>500.0</nits>
+ * </point>
+ * <point>
+ * <value>1.0</value>
+ * <nits>800.0</nits>
+ * </point>
+ * </screenBrightnessMap>
+ *
+ * <screenBrightnessDefault>0.65</screenBrightnessDefault>
+ *
+ * <thermalThrottling>
+ * <brightnessThrottlingMap>
+ * <brightnessThrottlingPoint>
+ * <thermalStatus>severe</thermalStatus>
+ * <brightness>0.1</brightness>
+ * </brightnessThrottlingPoint>
+ * <brightnessThrottlingPoint>
+ * <thermalStatus>critical</thermalStatus>
+ * <brightness>0.01</brightness>
+ * </brightnessThrottlingPoint>
+ * </brightnessThrottlingMap>
+ * </thermalThrottling>
+ *
+ * <highBrightnessMode enabled="true">
+ * <transitionPoint>0.62</transitionPoint>
+ * <minimumLux>10000</minimumLux>
+ * <timing>
+ * <timeWindowSecs>1800</timeWindowSecs> // Window in which we restrict HBM.
+ * <timeMaxSecs>300</timeMaxSecs> // Maximum time of HBM allowed in that window.
+ * <timeMinSecs>60</timeMinSecs> // Minimum time remaining required to switch
+ * </timing> // HBM on for.
+ * <refreshRate>
+ * <minimum>120</minimum>
+ * <maximum>120</maximum>
+ * </refreshRate>
+ * <thermalStatusLimit>light</thermalStatusLimit>
+ * <allowInLowPowerMode>false</allowInLowPowerMode>
+ * </highBrightnessMode>
+ *
+ * <quirks>
+ * <quirk>canSetBrightnessViaHwc</quirk>
+ * </quirks>
+ *
+ * <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
+ * <screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>
+ * <screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>
+ * <screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>
+ *
+ * <lightSensor>
+ * <type>android.sensor.light</type>
+ * <name>1234 Ambient Light Sensor</name>
+ * </lightSensor>
+ * <proxSensor>
+ * <type>android.sensor.proximity</type>
+ * <name>1234 Proximity Sensor</name>
+ * </proxSensor>
+ *
+ * <ambientLightHorizonLong>10001</ambientLightHorizonLong>
+ * <ambientLightHorizonShort>2001</ambientLightHorizonShort>
+ *
+ * <displayBrightnessChangeThresholds>
+ * <brighteningThresholds>
+ * <minimum>0.001</minimum> // Minimum change needed in screen brightness to brighten.
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * <minimum>0.002</minimum> // Minimum change needed in screen brightness to darken.
+ * </darkeningThresholds>
+ * </displayBrightnessChangeThresholds>
+ *
+ * <ambientBrightnessChangeThresholds>
+ * <brighteningThresholds>
+ * <minimum>0.003</minimum> // Minimum change needed in ambient brightness to brighten.
+ * </brighteningThresholds>
+ * <darkeningThresholds>
+ * <minimum>0.004</minimum> // Minimum change needed in ambient brightness to darken.
+ * </darkeningThresholds>
+ * </ambientBrightnessChangeThresholds>
+ *
+ * </displayConfiguration>
+ * }
+ * </pre>
*/
public class DisplayDeviceConfig {
private static final String TAG = "DisplayDeviceConfig";
+ private static final boolean DEBUG = false;
public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN;
@@ -83,6 +207,9 @@
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final int INTERPOLATION_DEFAULT = 0;
+ private static final int INTERPOLATION_LINEAR = 1;
+
// Float.NaN (used as invalid for brightness) cannot be stored in config.xml
// so -2 is used instead
private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
@@ -96,6 +223,9 @@
// Length of the ambient light horizon used to calculate short-term estimate of ambient light.
private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000;
+ @VisibleForTesting
+ static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -111,6 +241,7 @@
// config.xml. These are the raw values and just used for the dumpsys
private float[] mRawNits;
private float[] mRawBacklight;
+ private int mInterpolationType;
// These arrays are calculated from the raw arrays, but clamped to contain values equal to and
// between mBacklightMinimum and mBacklightMaximum. These three arrays should all be the same
@@ -139,12 +270,16 @@
private Spline mBrightnessToBacklightSpline;
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
+ private Spline mNitsToBacklightSpline;
private List<String> mQuirks;
private boolean mIsHighBrightnessModeEnabled = false;
private HighBrightnessModeData mHbmData;
private DensityMap mDensityMap;
private String mLoadedFrom = null;
+ private BrightnessThrottlingData mBrightnessThrottlingData;
+ private Spline mSdrToHdrRatioSpline;
+
private DisplayDeviceConfig(Context context) {
mContext = context;
}
@@ -328,6 +463,45 @@
}
/**
+ * Calculate the HDR brightness for the specified SDR brightenss.
+ *
+ * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
+ */
+ public float getHdrBrightnessFromSdr(float brightness) {
+ if (mSdrToHdrRatioSpline == null) {
+ return PowerManager.BRIGHTNESS_INVALID;
+ }
+
+ float backlight = getBacklightFromBrightness(brightness);
+ float nits = getNitsFromBacklight(backlight);
+ if (nits == NITS_INVALID) {
+ return PowerManager.BRIGHTNESS_INVALID;
+ }
+
+ float ratio = mSdrToHdrRatioSpline.interpolate(nits);
+ float hdrNits = nits * ratio;
+ if (mNitsToBacklightSpline == null) {
+ return PowerManager.BRIGHTNESS_INVALID;
+ }
+
+ float hdrBacklight = mNitsToBacklightSpline.interpolate(hdrNits);
+ hdrBacklight = Math.max(mBacklightMinimum, Math.min(mBacklightMaximum, hdrBacklight));
+ float hdrBrightness = mBacklightToBrightnessSpline.interpolate(hdrBacklight);
+
+ if (DEBUG) {
+ Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness
+ + " backlight " + backlight
+ + " nits " + nits
+ + " ratio " + ratio
+ + " hdrNits " + hdrNits
+ + " hdrBacklight " + hdrBacklight
+ + " hdrBrightness " + hdrBrightness
+ );
+ }
+ return hdrBrightness;
+ }
+
+ /**
* Return an array of equal length to backlight and nits, that covers the entire system
* brightness range of 0.0-1.0.
*
@@ -424,6 +598,13 @@
return mDensityMap;
}
+ /**
+ * @return brightness throttling data configuration data for the display.
+ */
+ public BrightnessThrottlingData getBrightnessThrottlingData() {
+ return BrightnessThrottlingData.create(mBrightnessThrottlingData);
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -432,15 +613,19 @@
+ ", mNits=" + Arrays.toString(mNits)
+ ", mRawBacklight=" + Arrays.toString(mRawBacklight)
+ ", mRawNits=" + Arrays.toString(mRawNits)
+ + ", mInterpolationType=" + mInterpolationType
+ ", mBrightness=" + Arrays.toString(mBrightness)
+ ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline
+ ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline
+ + ", mNitsToBacklightSpline=" + mNitsToBacklightSpline
+ ", mBacklightMinimum=" + mBacklightMinimum
+ ", mBacklightMaximum=" + mBacklightMaximum
+ ", mBrightnessDefault=" + mBrightnessDefault
+ ", mQuirks=" + mQuirks
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ ", mHbmData=" + mHbmData
+ + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
@@ -502,6 +687,7 @@
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMap(config);
+ loadBrightnessThrottlingMap(config);
loadHighBrightnessModeData(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -639,6 +825,7 @@
float[] nits = new float[size];
float[] backlight = new float[size];
+ mInterpolationType = convertInterpolationType(map.getInterpolation());
int i = 0;
for (Point point : points) {
nits[i] = point.getNits().floatValue();
@@ -664,6 +851,75 @@
constrainNitsAndBacklightArrays();
}
+ private Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) {
+ final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all();
+
+ if (sdrHdrRatioMap == null) {
+ return null;
+ }
+
+ final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint();
+ final int size = points.size();
+ if (size <= 0) {
+ return null;
+ }
+
+ float[] nits = new float[size];
+ float[] ratios = new float[size];
+
+ int i = 0;
+ for (SdrHdrRatioPoint point : points) {
+ nits[i] = point.getSdrNits().floatValue();
+ if (i > 0) {
+ if (nits[i] < nits[i - 1]) {
+ Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest "
+ + " of configuration. nits: " + nits[i] + " < "
+ + nits[i - 1]);
+ return null;
+ }
+ }
+ ratios[i] = point.getHdrRatio().floatValue();
+ ++i;
+ }
+
+ return Spline.createSpline(nits, ratios);
+ }
+
+ private void loadBrightnessThrottlingMap(DisplayConfiguration config) {
+ final ThermalThrottling throttlingConfig = config.getThermalThrottling();
+ if (throttlingConfig == null) {
+ Slog.i(TAG, "no thermal throttling config found");
+ return;
+ }
+
+ final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap();
+ if (map == null) {
+ Slog.i(TAG, "no brightness throttling map found");
+ return;
+ }
+
+ final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
+ // At least 1 point is guaranteed by the display device config schema
+ List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
+ new ArrayList<>(points.size());
+
+ boolean badConfig = false;
+ for (BrightnessThrottlingPoint point : points) {
+ ThermalStatus status = point.getThermalStatus();
+ if (!thermalStatusIsValid(status)) {
+ badConfig = true;
+ break;
+ }
+
+ throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+ convertThermalStatus(status), point.getBrightness().floatValue()));
+ }
+
+ if (!badConfig) {
+ mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -774,9 +1030,18 @@
mBacklight[mBacklight.length - 1],
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]);
}
- mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight);
- mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness);
- mBacklightToNitsSpline = Spline.createSpline(mBacklight, mNits);
+ mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
+ ? Spline.createLinearSpline(mBrightness, mBacklight)
+ : Spline.createSpline(mBrightness, mBacklight);
+ mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR
+ ? Spline.createLinearSpline(mBacklight, mBrightness)
+ : Spline.createSpline(mBacklight, mBrightness);
+ mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR
+ ? Spline.createLinearSpline(mBacklight, mNits)
+ : Spline.createSpline(mBacklight, mNits);
+ mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
+ ? Spline.createLinearSpline(mNits, mBacklight)
+ : Spline.createSpline(mNits, mBacklight);
}
private void loadQuirks(DisplayConfiguration config) {
@@ -813,6 +1078,20 @@
mRefreshRateLimitations.add(new RefreshRateLimitation(
DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max));
}
+ BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all();
+ if (minHdrPctOfScreen != null) {
+ mHbmData.minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue();
+ if (mHbmData.minimumHdrPercentOfScreen > 1
+ || mHbmData.minimumHdrPercentOfScreen < 0) {
+ Slog.w(TAG, "Invalid minimum HDR percent of screen: "
+ + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
+ mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+ }
+ } else {
+ mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+ }
+
+ mSdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm);
}
}
@@ -931,6 +1210,25 @@
}
}
+ private boolean thermalStatusIsValid(ThermalStatus value) {
+ if (value == null) {
+ return false;
+ }
+
+ switch (value) {
+ case none:
+ case light:
+ case moderate:
+ case severe:
+ case critical:
+ case emergency:
+ case shutdown:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
if (value == null) {
return PowerManager.THERMAL_STATUS_NONE;
@@ -956,6 +1254,21 @@
}
}
+ private int convertInterpolationType(Interpolation value) {
+ if (value == null) {
+ return INTERPOLATION_DEFAULT;
+ }
+ switch (value) {
+ case _default:
+ return INTERPOLATION_DEFAULT;
+ case linear:
+ return INTERPOLATION_LINEAR;
+ default:
+ Slog.wtf(TAG, "Unexpected Interpolation Type: " + value);
+ return INTERPOLATION_DEFAULT;
+ }
+ }
+
private void loadAmbientHorizonFromDdc(DisplayConfiguration config) {
final BigInteger configLongHorizon = config.getAmbientLightHorizonLong();
if (configLongHorizon != null) {
@@ -1020,11 +1333,15 @@
/** Minimum time that HBM can be on before being enabled. */
public long timeMinMillis;
+ /** Minimum HDR video size to enter high brightness mode */
+ public float minimumHdrPercentOfScreen;
+
HighBrightnessModeData() {}
HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
long timeMaxMillis, long timeMinMillis,
- @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode) {
+ @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode,
+ float minimumHdrPercentOfScreen) {
this.minimumLux = minimumLux;
this.transitionPoint = transitionPoint;
this.timeWindowMillis = timeWindowMillis;
@@ -1032,6 +1349,7 @@
this.timeMinMillis = timeMinMillis;
this.thermalStatusLimit = thermalStatusLimit;
this.allowInLowPowerMode = allowInLowPowerMode;
+ this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
}
/**
@@ -1046,6 +1364,7 @@
other.transitionPoint = transitionPoint;
other.thermalStatusLimit = thermalStatusLimit;
other.allowInLowPowerMode = allowInLowPowerMode;
+ other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
}
@Override
@@ -1058,7 +1377,95 @@
+ ", timeMin: " + timeMinMillis + "ms"
+ ", thermalStatusLimit: " + thermalStatusLimit
+ ", allowInLowPowerMode: " + allowInLowPowerMode
+ + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
+ "} ";
}
}
+
+ /**
+ * Container for brightness throttling data.
+ */
+ static class BrightnessThrottlingData {
+ static class ThrottlingLevel {
+ public @PowerManager.ThermalStatus int thermalStatus;
+ public float brightness;
+
+ ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+ this.thermalStatus = thermalStatus;
+ this.brightness = brightness;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + thermalStatus + "," + brightness + "]";
+ }
+ }
+
+ public List<ThrottlingLevel> throttlingLevels;
+
+ static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
+ {
+ if (throttlingLevels == null || throttlingLevels.size() == 0) {
+ Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
+ return null;
+ }
+
+ ThrottlingLevel prevLevel = throttlingLevels.get(0);
+ final int numLevels = throttlingLevels.size();
+ for (int i = 1; i < numLevels; i++) {
+ ThrottlingLevel thisLevel = throttlingLevels.get(i);
+
+ if (thisLevel.thermalStatus <= prevLevel.thermalStatus) {
+ Slog.e(TAG, "brightnessThrottlingMap must be strictly increasing, ignoring "
+ + "configuration. ThermalStatus " + thisLevel.thermalStatus + " <= "
+ + prevLevel.thermalStatus);
+ return null;
+ }
+
+ if (thisLevel.brightness >= prevLevel.brightness) {
+ Slog.e(TAG, "brightnessThrottlingMap must be strictly decreasing, ignoring "
+ + "configuration. Brightness " + thisLevel.brightness + " >= "
+ + thisLevel.brightness);
+ return null;
+ }
+
+ prevLevel = thisLevel;
+ }
+
+ for (ThrottlingLevel level : throttlingLevels) {
+ // Non-negative brightness values are enforced by device config schema
+ if (level.brightness > PowerManager.BRIGHTNESS_MAX) {
+ Slog.e(TAG, "brightnessThrottlingMap contains a brightness value exceeding "
+ + "system max. Brightness " + level.brightness + " > "
+ + PowerManager.BRIGHTNESS_MAX);
+ return null;
+ }
+ }
+
+ return new BrightnessThrottlingData(throttlingLevels);
+ }
+
+ static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
+ if (other == null)
+ return null;
+
+ return BrightnessThrottlingData.create(other.throttlingLevels);
+ }
+
+
+ @Override
+ public String toString() {
+ return "BrightnessThrottlingData{"
+ + "throttlingLevels:" + throttlingLevels
+ + "} ";
+ }
+
+ private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
+ throttlingLevels = new ArrayList<>(inLevels.size());
+ for (ThrottlingLevel level : inLevels) {
+ throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
+ }
+ }
+
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7ad4979..7f1482e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -141,7 +141,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -1756,10 +1755,6 @@
void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) {
synchronized (mSyncRoot) {
- if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) {
- return;
- }
-
if (mode != null && !isResolutionAndRefreshRateValid(mode)
&& displayId == Display.INVALID_DISPLAY) {
throw new IllegalArgumentException("width, height and refresh rate of mode should "
@@ -1813,7 +1808,15 @@
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- device.setUserPreferredDisplayModeLocked(mode);
+ // If there is a display specific mode, don't override that
+ final Point deviceUserPreferredResolution =
+ mPersistentDataStore.getUserPreferredResolution(device);
+ final float deviceRefreshRate =
+ mPersistentDataStore.getUserPreferredRefreshRate(device);
+ if (!isValidResolution(deviceUserPreferredResolution)
+ && !isValidRefreshRate(deviceRefreshRate)) {
+ device.setUserPreferredDisplayModeLocked(mode);
+ }
});
}
@@ -1830,6 +1833,16 @@
}
}
+ Display.Mode getSystemPreferredDisplayModeInternal(int displayId) {
+ synchronized (mSyncRoot) {
+ final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+ if (device == null) {
+ return null;
+ }
+ return device.getSystemPreferredDisplayModeLocked();
+ }
+ }
+
void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
mDisplayModeDirector.setShouldAlwaysRespectAppRequestedMode(enabled);
}
@@ -2180,6 +2193,16 @@
}
}
+ Display.Mode getActiveDisplayModeAtStart(int displayId) {
+ synchronized (mSyncRoot) {
+ final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+ if (device == null) {
+ return null;
+ }
+ return device.getActiveDisplayModeAtStartLocked();
+ }
+ }
+
void setAmbientColorTemperatureOverride(float cct) {
synchronized (mSyncRoot) {
final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
@@ -3468,6 +3491,16 @@
}
@Override // Binder call
+ public Display.Mode getSystemPreferredDisplayMode(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getSystemPreferredDisplayModeInternal(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
@@ -3533,6 +3566,14 @@
&& (brightness <= PowerManager.BRIGHTNESS_MAX);
}
+ private static boolean isValidResolution(Point resolution) {
+ return (resolution != null) && (resolution.x > 0) && (resolution.y > 0);
+ }
+
+ private static boolean isValidRefreshRate(float refreshRate) {
+ return !Float.isNaN(refreshRate) && (refreshRate > 0.0f);
+ }
+
private final class LocalService extends DisplayManagerInternal {
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index a9a1f08..bfdac57 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -68,6 +68,8 @@
return clearUserPreferredDisplayMode();
case "get-user-preferred-display-mode":
return getUserPreferredDisplayMode();
+ case "get-active-display-mode-at-start":
+ return getActiveDisplayModeAtStart();
case "set-match-content-frame-rate-pref":
return setMatchContentFrameRateUserPreference();
case "get-match-content-frame-rate-pref":
@@ -125,6 +127,9 @@
pw.println(" Returns the user preferred display mode or null if no mode is set by user."
+ "If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is "
+ "returned, else global display mode is returned.");
+ pw.println(" get-active-display-mode-at-start DISPLAY_ID");
+ pw.println(" Returns the display mode which was found at boot time of display with "
+ + "id = DISPLAY_ID");
pw.println(" set-match-content-frame-rate-pref PREFERENCE");
pw.println(" Sets the match content frame rate preference as PREFERENCE ");
pw.println(" get-match-content-frame-rate-pref");
@@ -298,6 +303,30 @@
return 0;
}
+ private int getActiveDisplayModeAtStart() {
+ final String displayIdText = getNextArg();
+ if (displayIdText == null) {
+ getErrPrintWriter().println("Error: no displayId specified");
+ return 1;
+ }
+ final int displayId;
+ try {
+ displayId = Integer.parseInt(displayIdText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid displayId");
+ return 1;
+ }
+
+ Display.Mode mode = mService.getActiveDisplayModeAtStart(displayId);
+ if (mode == null) {
+ getOutPrintWriter().println("Boot display mode: null");
+ return 0;
+ }
+ getOutPrintWriter().println("Boot display mode: " + mode.getPhysicalWidth() + " "
+ + mode.getPhysicalHeight() + " " + mode.getRefreshRate());
+ return 0;
+ }
+
private int setMatchContentFrameRateUserPreference() {
final String matchContentFrameRatePrefText = getNextArg();
if (matchContentFrameRatePrefText == null) {
@@ -335,7 +364,7 @@
}
private int setUserDisabledHdrTypes() {
- final String[] userDisabledHdrTypesText = getAllArgs();
+ String[] userDisabledHdrTypesText = peekRemainingArgs();
if (userDisabledHdrTypesText == null) {
getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
return 1;
@@ -351,7 +380,6 @@
getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
return 1;
}
-
final Context context = mService.getContext();
final DisplayManager dm = context.getSystemService(DisplayManager.class);
dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ec4b91a..d71e07a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -346,6 +346,7 @@
private boolean mAppliedTemporaryBrightness;
private boolean mAppliedTemporaryAutoBrightnessAdjustment;
private boolean mAppliedBrightnessBoost;
+ private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
// information.
@@ -379,6 +380,8 @@
private final HighBrightnessModeController mHbmController;
+ private final BrightnessThrottler mBrightnessThrottler;
+
private final BrightnessSetting mBrightnessSetting;
private final Runnable mOnBrightnessChangeRunnable;
@@ -538,31 +541,11 @@
mHbmController = createHbmControllerLocked();
+ mBrightnessThrottler = createBrightnessThrottlerLocked();
+
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
- setUpAutoBrightness(resources, handler);
-
- mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
- mColorFadeFadesConfig = resources.getBoolean(
- com.android.internal.R.bool.config_animateScreenLights);
-
- mDisplayBlanksAfterDozeConfig = resources.getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
-
- mBrightnessBucketsInDozeConfig = resources.getBoolean(
- com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
-
- loadProximitySensor();
-
- mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
- mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
- mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
- mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-
DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
DisplayWhiteBalanceController displayWhiteBalanceController = null;
if (mDisplayId == Display.DEFAULT_DISPLAY) {
@@ -605,6 +588,29 @@
} else {
mCdsi = null;
}
+
+ setUpAutoBrightness(resources, handler);
+
+ mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+ mColorFadeFadesConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_animateScreenLights);
+
+ mDisplayBlanksAfterDozeConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_displayBlanksAfterDoze);
+
+ mBrightnessBucketsInDozeConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
+
+ loadProximitySensor();
+
+ mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+
}
private void applyReduceBrightColorsSplineAdjustment(
@@ -826,7 +832,15 @@
setUpAutoBrightness(mContext.getResources(), mHandler);
reloadReduceBrightColours();
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
- mDisplayDeviceConfig.getHighBrightnessModeData());
+ mDisplayDeviceConfig.getHighBrightnessModeData(),
+ new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+ @Override
+ public float getHdrBrightnessFromSdr(float sdrBrightness) {
+ return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+ }
+ });
+ mBrightnessThrottler.resetThrottlingData(
+ mDisplayDeviceConfig.getBrightnessThrottlingData());
}
private void sendUpdatePowerState() {
@@ -894,7 +908,7 @@
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
- mDisplayDeviceConfig);
+ mDisplayDeviceConfig, mDisplayWhiteBalanceController);
if (isIdleScreenBrightnessEnabled) {
mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
mDisplayDeviceConfig, mDisplayWhiteBalanceController);
@@ -967,7 +981,7 @@
lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
- mHbmController, mIdleModeBrightnessMapper,
+ mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong());
} else {
@@ -1007,6 +1021,9 @@
mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
}
}
+ if (mDisplayWhiteBalanceController != null) {
+ mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+ }
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1039,6 +1056,7 @@
private void cleanupHandlerThreadAfterStop() {
setProximitySensorEnabled(false);
mHbmController.stop();
+ mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
if (mUnfinishedBusiness) {
mCallbacks.releaseSuspendBlocker();
@@ -1336,13 +1354,28 @@
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
}
- // The current brightness to use has been calculated at this point (minus the adjustments
- // like low-power and dim), and HbmController should be notified so that it can accurately
- // calculate HDR or HBM levels. We specifically do it here instead of having HbmController
- // listen to the brightness setting because certain brightness sources (just as an app
- // override) are not saved to the setting, but should be reflected in HBM
- // calculations.
- mHbmController.onBrightnessChanged(brightnessState);
+ // Now that a desired brightness has been calculated, apply brightness throttling. The
+ // dimming and low power transformations that follow can only dim brightness further.
+ //
+ // We didn't do this earlier through brightness clamping because we need to know both
+ // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+ // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+ // we broadcast this change through setting.
+ final float unthrottledBrightnessState = brightnessState;
+ if (mBrightnessThrottler.isThrottled()) {
+ brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ if (!mAppliedThrottling) {
+ // Brightness throttling is needed, so do so quickly.
+ // Later, when throttling is removed, we let other mechanisms decide on speed.
+ slowChange = false;
+ updateScreenBrightnessSetting = true;
+ }
+ mAppliedThrottling = true;
+ } else if (mAppliedThrottling) {
+ mAppliedThrottling = false;
+ updateScreenBrightnessSetting = true;
+ }
if (updateScreenBrightnessSetting) {
// Tell the rest of the system about the new brightness in case we had to change it
@@ -1390,6 +1423,14 @@
mAppliedLowPower = false;
}
+ // The current brightness to use has been calculated at this point, and HbmController should
+ // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
+ // here instead of having HbmController listen to the brightness setting because certain
+ // brightness sources (such as an app override) are not saved to the setting, but should be
+ // reflected in HBM calculations.
+ mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessThrottler.getBrightnessMaxReason());
+
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended or transition to/from VR.
boolean brightnessAdjusted = false;
@@ -1441,6 +1482,8 @@
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
float sdrAnimateValue = animateValue;
+ // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
+ // done in HighBrightnessModeController.
if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
|| (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
@@ -1618,7 +1661,8 @@
mCachedBrightnessInfo.brightnessMin.value,
mCachedBrightnessInfo.brightnessMax.value,
mCachedBrightnessInfo.hbmMode.value,
- mCachedBrightnessInfo.hbmTransitionPoint.value);
+ mCachedBrightnessInfo.hbmTransitionPoint.value,
+ mCachedBrightnessInfo.brightnessMaxReason.value);
}
}
@@ -1628,6 +1672,10 @@
private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
+ final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+ mBrightnessThrottler.getBrightnessCap());
+ final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+ mBrightnessThrottler.getBrightnessCap());
boolean changed = false;
changed |=
@@ -1638,16 +1686,19 @@
adjustedBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
- mHbmController.getCurrentBrightnessMin());
+ minBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
- mHbmController.getCurrentBrightnessMax());
+ maxBrightness);
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
mHbmController.getHighBrightnessMode());
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
mHbmController.getTransitionPoint());
+ changed |=
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+ mBrightnessThrottler.getBrightnessMaxReason());
return changed;
}
@@ -1669,6 +1720,12 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+ new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+ @Override
+ public float getHdrBrightnessFromSdr(float sdrBrightness) {
+ return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+ }
+ },
() -> {
sendUpdatePowerStateLocked();
postBrightnessChangeRunnable();
@@ -1679,6 +1736,18 @@
}, mContext);
}
+ private BrightnessThrottler createBrightnessThrottlerLocked() {
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+ final DisplayDeviceConfig.BrightnessThrottlingData data =
+ ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
+ return new BrightnessThrottler(mHandler, data,
+ () -> {
+ sendUpdatePowerStateLocked();
+ postBrightnessChangeRunnable();
+ });
+ }
+
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -2346,6 +2415,8 @@
pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
mCachedBrightnessInfo.hbmTransitionPoint.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
+ mCachedBrightnessInfo.brightnessMaxReason .value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2384,6 +2455,7 @@
pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
+ pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
pw.println(" mDozing=" + mDozing);
@@ -2422,6 +2494,10 @@
mHbmController.dump(pw);
}
+ if (mBrightnessThrottler != null) {
+ mBrightnessThrottler.dump(pw);
+ }
+
pw.println();
if (mDisplayWhiteBalanceController != null) {
mDisplayWhiteBalanceController.dump(pw);
@@ -2702,7 +2778,9 @@
static final int MODIFIER_DIMMED = 0x1;
static final int MODIFIER_LOW_POWER = 0x2;
static final int MODIFIER_HDR = 0x4;
- static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR;
+ static final int MODIFIER_THROTTLED = 0x8;
+ static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
+ | MODIFIER_THROTTLED;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -2777,6 +2855,9 @@
if ((modifier & MODIFIER_HDR) != 0) {
sb.append(" hdr");
}
+ if ((modifier & MODIFIER_THROTTLED) != 0) {
+ sb.append(" throttled");
+ }
int strlen = sb.length();
if (sb.charAt(strlen - 1) == '[') {
sb.setLength(strlen - 2);
@@ -2813,6 +2894,8 @@
public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
public MutableFloat hbmTransitionPoint =
new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+ public MutableInt brightnessMaxReason =
+ new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
public boolean checkAndSetFloat(MutableFloat mf, float f) {
if (mf.value != f) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index b3be894b..0b9d4de 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -58,11 +58,13 @@
private static final boolean DEBUG = false;
- private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
-
@VisibleForTesting
static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+ public interface HdrBrightnessDeviceConfig {
+ float getHdrBrightnessFromSdr(float sdrBrightness);
+ }
+
private final float mBrightnessMin;
private final float mBrightnessMax;
private final Handler mHandler;
@@ -76,13 +78,22 @@
private HdrListener mHdrListener;
private HighBrightnessModeData mHbmData;
+ private HdrBrightnessDeviceConfig mHdrBrightnessCfg;
private IBinder mRegisteredDisplayToken;
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
private boolean mIsAutoBrightnessEnabled = false;
private boolean mIsAutoBrightnessOffByState = false;
+
+ // The following values are typically reported by DisplayPowerController.
+ // This value includes brightness throttling effects.
private float mBrightness;
+ // This value excludes brightness throttling effects.
+ private float mUnthrottledBrightness;
+ private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
private boolean mIsHdrLayerPresent = false;
private boolean mIsThermalStatusWithinLimit = true;
@@ -107,16 +118,17 @@
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
String displayUniqueId, float brightnessMin, float brightnessMax,
- HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) {
+ HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
+ Runnable hbmChangeCallback, Context context) {
this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
- brightnessMax, hbmData, hbmChangeCallback, context);
+ brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context);
}
@VisibleForTesting
HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
- HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
- Context context) {
+ HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
+ Runnable hbmChangeCallback, Context context) {
mInjector = injector;
mContext = context;
mClock = injector.getClock();
@@ -130,7 +142,7 @@
mRecalcRunnable = this::recalculateTimeAllowance;
mHdrListener = new HdrListener();
- resetHbmData(width, height, displayToken, displayUniqueId, hbmData);
+ resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg);
}
void setAutoBrightnessEnabled(int state) {
@@ -170,6 +182,13 @@
}
float getHdrBrightnessValue() {
+ if (mHdrBrightnessCfg != null) {
+ float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness);
+ if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) {
+ return hdrBrightness;
+ }
+ }
+
// For HDR brightness, we take the current brightness and scale it to the max. The reason
// we do this is because we want brightness to go to HBM max when it would normally go
// to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
@@ -192,11 +211,14 @@
}
}
- void onBrightnessChanged(float brightness) {
+ void onBrightnessChanged(float brightness, float unthrottledBrightness,
+ @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
if (!deviceSupportsHbm()) {
return;
}
mBrightness = brightness;
+ mUnthrottledBrightness = unthrottledBrightness;
+ mThrottlingReason = throttlingReason;
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
@@ -239,10 +261,11 @@
}
void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
- HighBrightnessModeData hbmData) {
+ HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
mWidth = width;
mHeight = height;
mHbmData = hbmData;
+ mHdrBrightnessCfg = hdrBrightnessCfg;
mDisplayStatsId = displayUniqueId.hashCode();
unregisterHdrListener();
@@ -274,6 +297,8 @@
private void dumpLocal(PrintWriter pw) {
pw.println("HighBrightnessModeController:");
pw.println(" mBrightness=" + mBrightness);
+ pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness);
+ pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason));
pw.println(" mCurrentMin=" + getCurrentBrightnessMin());
pw.println(" mCurrentMax=" + getCurrentBrightnessMax());
pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
@@ -431,6 +456,9 @@
+ ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
+ ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
+ ", mBrightness: " + mBrightness
+ + ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ + ", mThrottlingReason: "
+ + BrightnessInfo.briMaxReasonToString(mThrottlingReason)
+ ", RunningStartTimeMillis: " + mRunningStartTimeMillis
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
+ ", events: " + mEvents);
@@ -446,31 +474,44 @@
private void updateHbmMode() {
int newHbmMode = calculateHighBrightnessMode();
- updateHbmStats(mHbmMode, newHbmMode);
+ updateHbmStats(newHbmMode);
if (mHbmMode != newHbmMode) {
mHbmMode = newHbmMode;
mHbmChangeCallback.run();
}
}
- private void updateHbmStats(int mode, int newMode) {
+ private void updateHbmStats(int newMode) {
+ final float transitionPoint = mHbmData.transitionPoint;
int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
- && getHdrBrightnessValue() > mHbmData.transitionPoint) {
+ && getHdrBrightnessValue() > transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
- } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
+ && mBrightness > transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
}
if (state == mHbmStatsState) {
return;
}
- mHbmStatsState = state;
int reason =
FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
- boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+ final boolean oldHbmSv = (mHbmStatsState
+ == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
+ final boolean newHbmSv =
+ (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
if (oldHbmSv && !newHbmSv) {
+ // HighBrightnessModeController (HBMC) currently supports throttling from two sources:
+ // 1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling()
+ // 2. External, received from HBMC.onBrightnessChanged()
+ // TODO(b/216373254): Deprecate internal throttling source
+ final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit;
+ final boolean externalThermalThrottling =
+ mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness...
+ mBrightness <= transitionPoint && // ...but we got NBM, because of...
+ mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals.
+
// If more than one conditions are flipped and turn off HBM sunlight
// visibility, only one condition will be reported to make it simple.
if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
@@ -483,7 +524,7 @@
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
} else if (!mIsTimeAvailable) {
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
- } else if (!mIsThermalStatusWithinLimit) {
+ } else if (internalThermalThrottling || externalThermalThrottling) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
} else if (mIsHdrLayerPresent) {
@@ -492,10 +533,15 @@
} else if (mIsBlockedByLowPowerMode) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
+ } else if (mBrightness <= mHbmData.transitionPoint) {
+ // This must be after external thermal check.
+ reason = FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
}
}
mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
+ mHbmStatsState = state;
}
private String hbmStatsStateToString(int hbmStatsState) {
@@ -568,11 +614,11 @@
int maxW, int maxH, int flags) {
mHandler.post(() -> {
mIsHdrLayerPresent = numberOfHdrLayers > 0
- && (float) (maxW * maxH)
- >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED);
+ && (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
+ * mHbmData.minimumHdrPercentOfScreen);
// Calling the brightness update so that we can recalculate
// brightness with HDR in mind.
- onBrightnessChanged(mBrightness);
+ onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
});
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3a9ef0a..a31c231 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -192,8 +192,12 @@
private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private int mDefaultModeId = INVALID_MODE_ID;
+ private int mSystemPreferredModeId = INVALID_MODE_ID;
private int mDefaultModeGroup;
private int mUserPreferredModeId = INVALID_MODE_ID;
+ // This is used only for the purpose of testing, to verify if the mode was correct when the
+ // device started or booted.
+ private int mActiveDisplayModeAtStartId = INVALID_MODE_ID;
private Display.Mode mUserPreferredMode;
private int mActiveModeId = INVALID_MODE_ID;
private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
@@ -208,7 +212,7 @@
private boolean mSidekickActive;
private SidekickInternal mSidekickInternal;
private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
- // The supported display modes according in SurfaceFlinger
+ // The supported display modes according to SurfaceFlinger
private SurfaceControl.DisplayMode[] mSfDisplayModes;
// The active display mode in SurfaceFlinger
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
@@ -230,6 +234,7 @@
mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,
mSurfaceControlProxy);
mDisplayDeviceConfig = null;
+ mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
}
@Override
@@ -238,12 +243,23 @@
}
/**
+ * Returns the boot display mode of this display.
+ * @hide
+ */
+ @Override
+ public Display.Mode getActiveDisplayModeAtStartLocked() {
+ return findMode(mActiveDisplayModeAtStartId);
+ }
+
+ /**
* Returns true if there is a change.
**/
public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo,
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
- boolean changed = updateDisplayModesLocked(
+ boolean changed =
+ updateSystemPreferredDisplayMode(dynamicInfo.preferredBootDisplayMode);
+ changed |= updateDisplayModesLocked(
dynamicInfo.supportedDisplayModes, dynamicInfo.activeDisplayModeId, modeSpecs);
changed |= updateStaticInfo(staticInfo);
changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
@@ -369,8 +385,11 @@
// For a new display, we need to initialize the default mode ID.
if (mDefaultModeId == INVALID_MODE_ID) {
- mDefaultModeId = activeRecord.mMode.getModeId();
- mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mDefaultModeId = mSystemPreferredModeId != INVALID_MODE_ID
+ ? mSystemPreferredModeId : activeRecord.mMode.getModeId();
+ mDefaultModeGroup = mSystemPreferredModeId != INVALID_MODE_ID
+ ? getModeById(mSfDisplayModes, mSystemPreferredModeId).group
+ : mActiveSfDisplayMode.group;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
@@ -531,6 +550,15 @@
return true;
}
+ private boolean updateSystemPreferredDisplayMode(int modeId) {
+ if (!mSurfaceControlProxy.getBootDisplayModeSupport()
+ || mSystemPreferredModeId == modeId) {
+ return false;
+ }
+ mSystemPreferredModeId = modeId;
+ return true;
+ }
+
private SurfaceControl.DisplayMode getModeById(SurfaceControl.DisplayMode[] supportedModes,
int modeId) {
for (SurfaceControl.DisplayMode mode : supportedModes) {
@@ -857,6 +885,16 @@
if (oldModeId != getPreferredModeId()) {
updateDeviceInfoLocked();
}
+
+ if (!mSurfaceControlProxy.getBootDisplayModeSupport()) {
+ return;
+ }
+ if (mUserPreferredMode == null) {
+ mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked());
+ } else {
+ mSurfaceControlProxy.setBootDisplayMode(getDisplayTokenLocked(),
+ mUserPreferredMode.getModeId());
+ }
}
@Override
@@ -865,6 +903,11 @@
}
@Override
+ public Display.Mode getSystemPreferredDisplayModeLocked() {
+ return findMode(mSystemPreferredModeId);
+ }
+
+ @Override
public void setRequestedColorModeLocked(int colorMode) {
requestColorModeLocked(colorMode);
}
@@ -1072,6 +1115,17 @@
return matchingModeId;
}
+ // Returns a mode with id = modeId.
+ private Display.Mode findMode(int modeId) {
+ for (int i = 0; i < mSupportedModes.size(); i++) {
+ Display.Mode supportedMode = mSupportedModes.valueAt(i).mMode;
+ if (supportedMode.getModeId() == modeId) {
+ return supportedMode;
+ }
+ }
+ return null;
+ }
+
// Returns a mode with resolution (width, height) and/or refreshRate. If any one of the
// resolution or refresh-rate is valid, a mode having the valid parameters is returned.
private Display.Mode findMode(int width, int height, float refreshRate) {
@@ -1318,6 +1372,18 @@
return SurfaceControl.setActiveColorMode(displayToken, colorMode);
}
+ public boolean getBootDisplayModeSupport() {
+ return SurfaceControl.getBootDisplayModeSupport();
+ }
+
+ public void setBootDisplayMode(IBinder displayToken, int modeId) {
+ SurfaceControl.setBootDisplayMode(displayToken, modeId);
+ }
+
+ public void clearBootDisplayMode(IBinder displayToken) {
+ SurfaceControl.clearBootDisplayMode(displayToken);
+ }
+
public void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
SurfaceControl.setAutoLowLatencyMode(displayToken, on);
@@ -1340,7 +1406,6 @@
return SurfaceControl.setDisplayBrightness(displayToken, sdrBacklight, sdrNits,
displayBacklight, displayNits);
}
-
}
static class BacklightAdapter {
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index ed3b15f..d8672fc 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -23,6 +23,8 @@
/**
* A custom animator that progressively updates a property value at
* a given variable rate until it reaches a particular target value.
+ * The ramping at the given rate is done in the perceptual space using
+ * the HLG transfer functions.
*/
class RampAnimator<T> {
private final T mObject;
@@ -57,7 +59,9 @@
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if the target differs from the previous target.
*/
- public boolean animateTo(float target, float rate) {
+ public boolean animateTo(float targetLinear, float rate) {
+ // Convert the target from the linear into the HLG space.
+ final float target = BrightnessUtils.convertLinearToGamma(targetLinear);
// Immediately jump to the target the first time.
if (mFirstTime || rate <= 0) {
@@ -66,7 +70,7 @@
mRate = 0;
mTargetValue = target;
mCurrentValue = target;
- mProperty.setValue(mObject, target);
+ setPropertyValue(target);
if (mAnimating) {
mAnimating = false;
cancelAnimationCallback();
@@ -121,6 +125,15 @@
mListener = listener;
}
+ /**
+ * Sets the brightness property by converting the given value from HLG space
+ * into linear space.
+ */
+ private void setPropertyValue(float val) {
+ final float linearVal = BrightnessUtils.convertGammaToLinear(val);
+ mProperty.setValue(mObject, linearVal);
+ }
+
private void postAnimationCallback() {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null);
}
@@ -156,7 +169,7 @@
final float oldCurrentValue = mCurrentValue;
mCurrentValue = mAnimatedValue;
if (oldCurrentValue != mCurrentValue) {
- mProperty.setValue(mObject, mCurrentValue);
+ setPropertyValue(mCurrentValue);
}
if (mTargetValue != mCurrentValue) {
postAnimationCallback();
@@ -201,14 +214,14 @@
* If this is the first time the property is being set or if the rate is 0,
* the value jumps directly to the target.
*
- * @param firstTarget The first target value.
- * @param secondTarget The second target value.
+ * @param linearFirstTarget The first target value in linear space.
+ * @param linearSecondTarget The second target value in linear space.
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if either target differs from the previous target.
*/
- public boolean animateTo(float firstTarget, float secondTarget, float rate) {
- final boolean firstRetval = mFirst.animateTo(firstTarget, rate);
- final boolean secondRetval = mSecond.animateTo(secondTarget, rate);
+ public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) {
+ final boolean firstRetval = mFirst.animateTo(linearFirstTarget, rate);
+ final boolean secondRetval = mSecond.animateTo(linearSecondTarget, rate);
return firstRetval && secondRetval;
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 151ec81..fb36dc7 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -44,26 +44,18 @@
AmbientSensor.AmbientBrightnessSensor.Callbacks,
AmbientSensor.AmbientColorTemperatureSensor.Callbacks {
- protected static final String TAG = "DisplayWhiteBalanceController";
- protected boolean mLoggingEnabled;
+ private static final String TAG = "DisplayWhiteBalanceController";
+ private boolean mLoggingEnabled;
- private boolean mEnabled;
+ private final ColorDisplayServiceInternal mColorDisplayServiceInternal;
- // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
- // implements Callbacks and passes itself to the DWBC so it can call back into it without
- // knowing about it.
- private Callbacks mCallbacks;
-
- private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
-
+ private final AmbientSensor.AmbientBrightnessSensor mBrightnessSensor;
@VisibleForTesting
AmbientFilter mBrightnessFilter;
- private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
-
+ private final AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor;
@VisibleForTesting
AmbientFilter mColorTemperatureFilter;
- private DisplayWhiteBalanceThrottler mThrottler;
-
+ private final DisplayWhiteBalanceThrottler mThrottler;
// In low brightness conditions the ALS readings are more noisy and produce
// high errors. This default is introduced to provide a fixed display color
// temperature when sensor readings become unreliable.
@@ -74,16 +66,12 @@
private final float mHighLightAmbientColorTemperature;
private float mAmbientColorTemperature;
-
@VisibleForTesting
float mPendingAmbientColorTemperature;
private float mLastAmbientColorTemperature;
- private ColorDisplayServiceInternal mColorDisplayServiceInternal;
-
// The most recent ambient color temperature values are kept for debugging purposes.
- private static final int HISTORY_SIZE = 50;
- private History mAmbientColorTemperatureHistory;
+ private final History mAmbientColorTemperatureHistory;
// Override the ambient color temperature for debugging purposes.
private float mAmbientColorTemperatureOverride;
@@ -91,6 +79,10 @@
// A piecewise linear relationship between ambient and display color temperatures.
private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
+ // A piecewise linear relationship between ambient and display color temperatures, with a
+ // stronger change between the two sets of values.
+ private Spline.LinearSpline mStrongAmbientToDisplayColorTemperatureSpline;
+
// In very low or very high brightness conditions Display White Balance should
// be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline.
// However, setting Display White Balance based on thresholds can cause the
@@ -109,6 +101,17 @@
private float mLatestLowLightBias;
private float mLatestHighLightBias;
+ private boolean mEnabled;
+
+ // Whether a higher-strength adjustment should be applied; this must be enabled in addition to
+ // mEnabled in order to be applied.
+ private boolean mStrongModeEnabled;
+
+ // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC
+ // implements Callbacks and passes itself to the DWBC so it can call back into it without
+ // knowing about it.
+ private Callbacks mDisplayPowerControllerCallbacks;
+
/**
* @param brightnessSensor
* The sensor used to detect changes in the ambient brightness.
@@ -159,16 +162,18 @@
@NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor,
@NonNull AmbientFilter colorTemperatureFilter,
@NonNull DisplayWhiteBalanceThrottler throttler,
- float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases,
+ float[] lowLightAmbientBrightnesses,
+ float[] lowLightAmbientBiases,
float lowLightAmbientColorTemperature,
- float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases,
+ float[] highLightAmbientBrightnesses,
+ float[] highLightAmbientBiases,
float highLightAmbientColorTemperature,
- float[] ambientColorTemperatures, float[] displayColorTemperatures) {
+ float[] ambientColorTemperatures,
+ float[] displayColorTemperatures,
+ float[] strongAmbientColorTemperatures,
+ float[] strongDisplayColorTemperatures) {
validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor,
colorTemperatureFilter, throttler);
- mLoggingEnabled = false;
- mEnabled = false;
- mCallbacks = null;
mBrightnessSensor = brightnessSensor;
mBrightnessFilter = brightnessFilter;
mColorTemperatureSensor = colorTemperatureSensor;
@@ -179,7 +184,7 @@
mAmbientColorTemperature = -1.0f;
mPendingAmbientColorTemperature = -1.0f;
mLastAmbientColorTemperature = -1.0f;
- mAmbientColorTemperatureHistory = new History(HISTORY_SIZE);
+ mAmbientColorTemperatureHistory = new History(/* size= */ 50);
mAmbientColorTemperatureOverride = -1.0f;
try {
@@ -235,6 +240,13 @@
mAmbientToDisplayColorTemperatureSpline = null;
}
+ try {
+ mStrongAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline(
+ strongAmbientColorTemperatures, strongDisplayColorTemperatures);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to create strong ambient to display color temperature spline", e);
+ }
+
mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class);
}
@@ -255,6 +267,19 @@
}
/**
+ * Enable/disable the stronger adjustment option.
+ *
+ * @param enabled whether the stronger adjustment option should be turned on
+ */
+ public void setStrongModeEnabled(boolean enabled) {
+ mStrongModeEnabled = enabled;
+ if (mEnabled) {
+ updateAmbientColorTemperature();
+ updateDisplayColorTemperature();
+ }
+ }
+
+ /**
* Set an object to call back to when the display color temperature should be updated.
*
* @param callbacks
@@ -263,10 +288,10 @@
* @return Whether the method succeeded or not.
*/
public boolean setCallbacks(Callbacks callbacks) {
- if (mCallbacks == callbacks) {
+ if (mDisplayPowerControllerCallbacks == callbacks) {
return false;
}
- mCallbacks = callbacks;
+ mDisplayPowerControllerCallbacks = callbacks;
return true;
}
@@ -321,7 +346,7 @@
writer.println("DisplayWhiteBalanceController");
writer.println(" mLoggingEnabled=" + mLoggingEnabled);
writer.println(" mEnabled=" + mEnabled);
- writer.println(" mCallbacks=" + mCallbacks);
+ writer.println(" mDisplayPowerControllerCallbacks=" + mDisplayPowerControllerCallbacks);
mBrightnessSensor.dump(writer);
mBrightnessFilter.dump(writer);
mColorTemperatureSensor.dump(writer);
@@ -336,6 +361,8 @@
writer.println(" mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride);
writer.println(" mAmbientToDisplayColorTemperatureSpline="
+ mAmbientToDisplayColorTemperatureSpline);
+ writer.println(" mStrongAmbientToDisplayColorTemperatureSpline="
+ + mStrongAmbientToDisplayColorTemperatureSpline);
writer.println(" mLowLightAmbientBrightnessToBiasSpline="
+ mLowLightAmbientBrightnessToBiasSpline);
writer.println(" mHighLightAmbientBrightnessToBiasSpline="
@@ -364,9 +391,20 @@
float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
mLatestAmbientColorTemperature = ambientColorTemperature;
- if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
- ambientColorTemperature =
- mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature);
+ if (mStrongModeEnabled) {
+ if (mStrongAmbientToDisplayColorTemperatureSpline != null
+ && ambientColorTemperature != -1.0f) {
+ ambientColorTemperature =
+ mStrongAmbientToDisplayColorTemperatureSpline.interpolate(
+ ambientColorTemperature);
+ }
+ } else {
+ if (mAmbientToDisplayColorTemperatureSpline != null
+ && ambientColorTemperature != -1.0f) {
+ ambientColorTemperature =
+ mAmbientToDisplayColorTemperatureSpline.interpolate(
+ ambientColorTemperature);
+ }
}
float ambientBrightness = mBrightnessFilter.getEstimate(time);
@@ -409,8 +447,8 @@
Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature);
}
mPendingAmbientColorTemperature = ambientColorTemperature;
- if (mCallbacks != null) {
- mCallbacks.updateWhiteBalance();
+ if (mDisplayPowerControllerCallbacks != null) {
+ mDisplayPowerControllerCallbacks.updateWhiteBalance();
}
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index a72b1ed..07821b0 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -87,15 +87,22 @@
.config_displayWhiteBalanceHighLightAmbientColorTemperature);
final float[] ambientColorTemperatures = getFloatArray(resources,
com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures);
- final float[] displayColorTempeartures = getFloatArray(resources,
+ final float[] displayColorTemperatures = getFloatArray(resources,
com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures);
+ final float[] strongAmbientColorTemperatures = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceStrongAmbientColorTemperatures);
+ final float[] strongDisplayColorTemperatures = getFloatArray(resources,
+ com.android.internal.R.array
+ .config_displayWhiteBalanceStrongDisplayColorTemperatures);
final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController(
brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter,
throttler, displayWhiteBalanceLowLightAmbientBrightnesses,
displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature,
displayWhiteBalanceHighLightAmbientBrightnesses,
displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature,
- ambientColorTemperatures, displayColorTempeartures);
+ ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures,
+ strongDisplayColorTemperatures);
brightnessSensor.setCallbacks(controller);
colorTemperatureSensor.setCallbacks(controller);
return controller;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 258689a..f0a6af3 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -416,13 +416,14 @@
mCurrentDreamCanDoze = canDoze;
mCurrentDreamUserId = userId;
+ if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
+ mUiEventLogger.log(DreamManagerEvent.DREAM_START);
+ }
+
PowerManager.WakeLock wakeLock = mPowerManager
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
mHandler.post(wakeLock.wrap(() -> {
mAtmInternal.notifyDreamStateChanged(true);
- if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
- mUiEventLogger.log(DreamManagerEvent.DREAM_START);
- }
mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
mDreamOverlayServiceName);
}));
@@ -484,14 +485,15 @@
}
private static String componentsToString(ComponentName[] componentNames) {
+ if (componentNames == null) {
+ return null;
+ }
StringBuilder names = new StringBuilder();
- if (componentNames != null) {
- for (ComponentName componentName : componentNames) {
- if (names.length() > 0) {
- names.append(',');
- }
- names.append(componentName.flattenToString());
+ for (ComponentName componentName : componentNames) {
+ if (names.length() > 0) {
+ names.append(',');
}
+ names.append(componentName.flattenToString());
}
return names.toString();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 65ec1c0..79820a2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -29,6 +29,7 @@
import android.os.SystemProperties;
import android.provider.Settings.Global;
import android.util.ArrayMap;
+import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -246,9 +247,11 @@
mAllowedValues.add(value);
if (mContext.getResources().getBoolean(defaultResId)) {
if (mDefaultValue != null) {
- throw new VerificationException("Invalid CEC setup for '"
- + this.getName() + "' setting. "
- + "Setting already has a default value.");
+ Slog.e(TAG,
+ "Failed to set '" + value + "' as a default for '" + this.getName()
+ + "': Setting already has a default ('" + mDefaultValue
+ + "').");
+ return;
}
mDefaultValue = value;
}
@@ -277,6 +280,11 @@
mContext = context;
mStorageAdapter = storageAdapter;
+ // IMPORTANT: when adding a config value for a particular setting, register that value AFTER
+ // the existing values for that setting. That way, defaults set in the RRO are forward
+ // compatible even if the RRO doesn't include that new value yet
+ // (e.g. because it's ported from a previous release).
+
Setting hdmiCecEnabled = registerSetting(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
R.bool.config_cecHdmiCecEnabled_userConfigurable);
@@ -313,15 +321,15 @@
powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV,
R.bool.config_cecPowerControlModeTv_allowed,
R.bool.config_cecPowerControlModeTv_default);
- powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM,
- R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed,
- R.bool.config_cecPowerControlModeTvAndAudioSystem_default);
powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST,
R.bool.config_cecPowerControlModeBroadcast_allowed,
R.bool.config_cecPowerControlModeBroadcast_default);
powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_NONE,
R.bool.config_cecPowerControlModeNone_allowed,
R.bool.config_cecPowerControlModeNone_default);
+ powerControlMode.registerValue(HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM,
+ R.bool.config_cecPowerControlModeTvAndAudioSystem_allowed,
+ R.bool.config_cecPowerControlModeTvAndAudioSystem_default);
Setting powerStateChangeOnActiveSourceLost = registerSetting(
HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -385,6 +393,16 @@
R.bool.config_cecTvSendStandbyOnSleepDisabled_allowed,
R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
+ Setting setMenuLanguage = registerSetting(
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
+ R.bool.config_cecSetMenuLanguage_userConfigurable);
+ setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_ENABLED,
+ R.bool.config_cecSetMenuLanguageEnabled_allowed,
+ R.bool.config_cecSetMenuLanguageEnabled_default);
+ setMenuLanguage.registerValue(HdmiControlManager.SET_MENU_LANGUAGE_DISABLED,
+ R.bool.config_cecSetMenuLanguageDisabled_allowed,
+ R.bool.config_cecSetMenuLanguageDisabled_default);
+
Setting rcProfileTv = registerSetting(
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
R.bool.config_cecRcProfileTv_userConfigurable);
@@ -697,6 +715,8 @@
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
return STORAGE_SHARED_PREFS;
+ case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+ return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return STORAGE_SHARED_PREFS;
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -768,6 +788,8 @@
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
return setting.getName();
+ case HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE:
+ return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
return setting.getName();
case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c674ffe..454a76a 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -901,17 +901,12 @@
protected int handleVendorCommandWithId(HdmiCecMessage message) {
byte[] params = message.getParams();
int vendorId = HdmiUtils.threeBytesToInt(params);
- if (vendorId == mService.getVendorId()) {
- if (!mService.invokeVendorCommandListenersOnReceived(
- mDeviceType, message.getSource(), message.getDestination(), params, true)) {
- return Constants.ABORT_REFUSED;
- }
- } else if (message.getDestination() != Constants.ADDR_BROADCAST
- && message.getSource() != Constants.ADDR_UNREGISTERED) {
- Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
- return Constants.ABORT_UNRECOGNIZED_OPCODE;
- } else {
+ if (message.getDestination() == Constants.ADDR_BROADCAST
+ || message.getSource() == Constants.ADDR_UNREGISTERED) {
Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
+ } else if (!mService.invokeVendorCommandListenersOnReceived(
+ mDeviceType, message.getSource(), message.getDestination(), params, true)) {
+ return Constants.ABORT_REFUSED;
}
return Constants.HANDLED;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b23395f..0edcea5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -17,10 +17,15 @@
package com.android.server.hdmi;
import android.annotation.CallSuper;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.Binder;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -45,9 +50,6 @@
public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDevicePlayback";
- private static final boolean SET_MENU_LANGUAGE =
- HdmiProperties.set_menu_language_enabled().orElse(false);
-
// How long to wait after hotplug out before possibly going to Standby.
@VisibleForTesting
static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
@@ -383,7 +385,9 @@
@Constants.HandleMessageResult
protected int handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (!SET_MENU_LANGUAGE) {
+ if (mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE)
+ == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) {
return Constants.ABORT_UNRECOGNIZED_OPCODE;
}
@@ -408,7 +412,7 @@
// locale from being chosen. 'eng' in the CEC command, for instance,
// will always be mapped to en-AU among other variants like en-US, en-GB,
// an en-IN, which may not be the expected one.
- LocalePicker.updateLocale(localeInfo.getLocale());
+ startSetMenuLanguageActivity(localeInfo.getLocale());
return Constants.HANDLED;
}
}
@@ -420,6 +424,24 @@
}
}
+ private void startSetMenuLanguageActivity(Locale locale) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Context context = mService.getContext();
+ Intent intent = new Intent();
+ intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
+ intent.setComponent(
+ ComponentName.unflattenFromString(context.getResources().getString(
+ com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivityAsUser(intent, context.getUser());
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
@Constants.HandleMessageResult
protected int handleSetSystemAudioMode(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index afcd3dd..1ce36b1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -41,7 +41,10 @@
import android.hardware.hdmi.HdmiTimerRecordSources;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.media.AudioSystem;
+import android.media.AudioDescriptor;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioProfile;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager.TvInputCallback;
import android.util.Slog;
@@ -58,6 +61,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Represent a logical device of type TV residing in Android system.
@@ -816,12 +820,23 @@
HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
boolean oldStatus = mArcEstablished;
- // 1. Enable/disable ARC circuit.
- enableAudioReturnChannel(enabled);
- // 2. Notify arc status to audio service.
- notifyArcStatusToAudioService(enabled);
- // 3. Update arc status;
- mArcEstablished = enabled;
+ if (enabled) {
+ RequestSadAction action = new RequestSadAction(
+ this, Constants.ADDR_AUDIO_SYSTEM,
+ new RequestSadAction.RequestSadCallback() {
+ @Override
+ public void onRequestSadDone(List<byte[]> supportedSads) {
+ enableAudioReturnChannel(enabled);
+ notifyArcStatusToAudioService(enabled, supportedSads);
+ mArcEstablished = enabled;
+ }
+ });
+ addAndStartAction(action);
+ } else {
+ enableAudioReturnChannel(enabled);
+ notifyArcStatusToAudioService(enabled, new ArrayList<>());
+ mArcEstablished = enabled;
+ }
return oldStatus;
}
@@ -843,11 +858,15 @@
return mService.isConnected(portId);
}
- private void notifyArcStatusToAudioService(boolean enabled) {
+ private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) {
// Note that we don't set any name to ARC.
- mService.getAudioManager().setWiredDeviceConnectionState(
- AudioSystem.DEVICE_OUT_HDMI_ARC,
- enabled ? 1 : 0, "", "");
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "",
+ new ArrayList<AudioProfile>(), supportedSads.stream()
+ .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID,
+ AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad))
+ .collect(Collectors.toList()));
+ mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
index 6f7473d..57fe9e6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java
@@ -133,6 +133,7 @@
addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser);
addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser);
addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser);
+ addHandler(Constants.MESSAGE_GIVE_FEATURES, mBypasser);
addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8391e0b..8ac2331 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1701,11 +1701,11 @@
class VendorCommandListenerRecord implements IBinder.DeathRecipient {
private final IHdmiVendorCommandListener mListener;
- private final int mDeviceType;
+ private final int mVendorId;
- public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
+ VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId) {
mListener = listener;
- mDeviceType = deviceType;
+ mVendorId = vendorId;
}
@Override
@@ -2191,10 +2191,10 @@
}
@Override
- public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
- final int deviceType) {
+ public void addVendorCommandListener(
+ final IHdmiVendorCommandListener listener, final int vendorId) {
initBinderCall();
- HdmiControlService.this.addVendorCommandListener(listener, deviceType);
+ HdmiControlService.this.addVendorCommandListener(listener, vendorId);
}
@Override
@@ -3354,8 +3354,9 @@
mStandbyMessageReceived = false;
}
- private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
- VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
+ @VisibleForTesting
+ void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {
+ VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId);
try {
listener.asBinder().linkToDeath(record, 0);
} catch (RemoteException e) {
@@ -3374,8 +3375,14 @@
return false;
}
for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
- if (record.mDeviceType != deviceType) {
- continue;
+ if (hasVendorId) {
+ int vendorId =
+ ((params[0] & 0xFF) << 16)
+ + ((params[1] & 0xFF) << 8)
+ + (params[2] & 0xFF);
+ if (record.mVendorId != vendorId) {
+ continue;
+ }
}
try {
record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
new file mode 100644
index 0000000..dbd3f35
--- /dev/null
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import android.os.IBinder;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * An internal implementation of an {@link InputMonitor} that uses a spy window.
+ *
+ * This spy window is a layer container in the SurfaceFlinger hierarchy that does not have any
+ * graphical buffer, but can still receive input. It is parented to the DisplayContent so
+ * that it can spy on any pointer events that start in the DisplayContent bounds. When the
+ * object is constructed, it will add itself to SurfaceFlinger.
+ */
+class GestureMonitorSpyWindow {
+ final InputApplicationHandle mApplicationHandle;
+ final InputWindowHandle mWindowHandle;
+
+ // The token, InputChannel, and SurfaceControl are owned by this object.
+ final IBinder mMonitorToken;
+ final InputChannel mClientChannel;
+ final SurfaceControl mInputSurface;
+
+ GestureMonitorSpyWindow(IBinder token, String name, int displayId, int pid, int uid,
+ SurfaceControl sc, InputChannel inputChannel) {
+ mMonitorToken = token;
+ mClientChannel = inputChannel;
+ mInputSurface = sc;
+
+ mApplicationHandle = new InputApplicationHandle(null, name,
+ DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
+ 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 = pid;
+ mWindowHandle.ownerUid = uid;
+ mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ mWindowHandle.scaleFactor = 1.0f;
+ mWindowHandle.trustedOverlay = true;
+ mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setInputWindowInfo(mInputSurface, mWindowHandle);
+ t.setLayer(mInputSurface, Integer.MAX_VALUE);
+ t.setPosition(mInputSurface, 0, 0);
+ t.setCrop(mInputSurface, null /* crop to parent surface */);
+ t.show(mInputSurface);
+
+ t.apply();
+ }
+
+ void remove() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.hide(mInputSurface);
+ t.remove(mInputSurface);
+ t.apply();
+
+ mClientChannel.dispose();
+ }
+
+ String dump() {
+ return "name='" + mWindowHandle.name + "', inputChannelToken="
+ + mClientChannel.getToken() + " displayId=" + mWindowHandle.displayId;
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index b9c7123..354d183 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -277,6 +277,12 @@
@GuardedBy("mPointerDisplayIdLock")
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+ // Holds all the registered gesture monitors that are implemented as spy windows. The spy
+ // windows are mapped by their InputChannel tokens.
+ @GuardedBy("mInputMonitors")
+ final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
+
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -295,7 +301,7 @@
int locationKeyCode);
private static native InputChannel nativeCreateInputChannel(long ptr, String name);
private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
- boolean isGestureMonitor, String name, int pid);
+ String name, int pid);
private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken);
private static native void nativePilferPointers(long ptr, IBinder token);
private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
@@ -712,37 +718,77 @@
throw new IllegalArgumentException("displayId must >= 0.");
}
- return nativeCreateInputMonitor(mPtr, displayId, false /* isGestureMonitor */,
- inputChannelName, Binder.getCallingPid());
+ return nativeCreateInputMonitor(mPtr, displayId, inputChannelName, Binder.getCallingPid());
+ }
+
+ @NonNull
+ private InputChannel createSpyWindowGestureMonitor(IBinder monitorToken, String name,
+ int displayId, int pid, int uid) {
+ final SurfaceControl sc = mWindowManagerCallbacks.createSurfaceForGestureMonitor(name,
+ displayId);
+ if (sc == null) {
+ throw new IllegalArgumentException(
+ "Could not create gesture monitor surface on display: " + displayId);
+ }
+ final InputChannel channel = createInputChannel(name);
+
+ try {
+ monitorToken.linkToDeath(() -> removeSpyWindowGestureMonitor(channel.getToken()), 0);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Client died before '" + name + "' could be created.");
+ return null;
+ }
+ synchronized (mInputMonitors) {
+ mInputMonitors.put(channel.getToken(),
+ new GestureMonitorSpyWindow(monitorToken, name, displayId, pid, uid, sc,
+ channel));
+ }
+
+ final InputChannel outInputChannel = new InputChannel();
+ channel.copyTo(outInputChannel);
+ return outInputChannel;
+ }
+
+ private void removeSpyWindowGestureMonitor(IBinder inputChannelToken) {
+ final GestureMonitorSpyWindow monitor;
+ synchronized (mInputMonitors) {
+ monitor = mInputMonitors.remove(inputChannelToken);
+ }
+ removeInputChannel(inputChannelToken);
+ if (monitor == null) return;
+ monitor.remove();
}
/**
* Creates an input monitor that will receive pointer events for the purposes of system-wide
* gesture interpretation.
*
- * @param inputChannelName The input channel name.
+ * @param requestedName The input channel name.
* @param displayId Target display id.
* @return The input channel.
*/
@Override // Binder call
- public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
+ public InputMonitor monitorGestureInput(IBinder monitorToken, @NonNull String requestedName,
+ int displayId) {
if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
- "monitorInputRegion()")) {
+ "monitorGestureInput()")) {
throw new SecurityException("Requires MONITOR_INPUT permission");
}
- Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");
+ Objects.requireNonNull(requestedName, "name must not be null.");
+ Objects.requireNonNull(monitorToken, "token must not be null.");
if (displayId < Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("displayId must >= 0.");
}
+ final String name = "[Gesture Monitor] " + requestedName;
final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- InputChannel inputChannel = nativeCreateInputMonitor(
- mPtr, displayId, true /*isGestureMonitor*/, inputChannelName, pid);
- InputMonitorHost host = new InputMonitorHost(inputChannel.getToken());
- return new InputMonitor(inputChannel, host);
+ final InputChannel inputChannel =
+ createSpyWindowGestureMonitor(monitorToken, name, displayId, pid, uid);
+ return new InputMonitor(inputChannel, new InputMonitorHost(inputChannel.getToken()));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2285,14 +2331,8 @@
nativeNotifyPortAssociationsChanged(mPtr);
}
- /**
- * Add a runtime association between the input device name and the display unique id.
- * @param inputDeviceName The name of the input device.
- * @param displayUniqueId The unique id of the associated display.
- */
@Override // Binder call
- public void addUniqueIdAssociation(@NonNull String inputDeviceName,
- @NonNull String displayUniqueId) {
+ public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"addNameAssociation()")) {
@@ -2300,20 +2340,16 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
Objects.requireNonNull(displayUniqueId);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.put(inputDeviceName, displayUniqueId);
+ mUniqueIdAssociations.put(inputPort, displayUniqueId);
}
nativeChangeUniqueIdAssociation(mPtr);
}
- /**
- * Remove the runtime association between the input device and the display.
- * @param inputDeviceName The port of the input device to be cleared.
- */
@Override // Binder call
- public void removeUniqueIdAssociation(@NonNull String inputDeviceName) {
+ public void removeUniqueIdAssociation(@NonNull String inputPort) {
if (!checkCallingPermission(
android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
"removeUniqueIdAssociation()")) {
@@ -2321,9 +2357,9 @@
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
- Objects.requireNonNull(inputDeviceName);
+ Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputDeviceName);
+ mUniqueIdAssociations.remove(inputPort);
}
nativeChangeUniqueIdAssociation(mPtr);
}
@@ -2573,27 +2609,48 @@
String dumpStr = nativeDump(mPtr);
if (dumpStr != null) {
pw.println(dumpStr);
- dumpAssociations(pw);
}
+
+ pw.println("Input Manager Service (Java) State:");
+ dumpAssociations(pw, " " /*prefix*/);
+ dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
}
- private void dumpAssociations(PrintWriter pw) {
+ private void dumpAssociations(PrintWriter pw, String prefix) {
if (!mStaticAssociations.isEmpty()) {
- pw.println("Static Associations:");
+ pw.println(prefix + "Static Associations:");
mStaticAssociations.forEach((k, v) -> {
- pw.print(" port: " + k);
+ pw.print(prefix + " port: " + k);
pw.println(" display: " + v);
});
}
synchronized (mAssociationsLock) {
if (!mRuntimeAssociations.isEmpty()) {
- pw.println("Runtime Associations:");
+ pw.println(prefix + "Runtime Associations:");
mRuntimeAssociations.forEach((k, v) -> {
- pw.print(" port: " + k);
+ pw.print(prefix + " port: " + k);
pw.println(" display: " + v);
});
}
+ if (!mUniqueIdAssociations.isEmpty()) {
+ pw.println(prefix + "Unique Id Associations:");
+ mUniqueIdAssociations.forEach((k, v) -> {
+ pw.print(prefix + " port: " + k);
+ pw.println(" uniqueId: " + v);
+ });
+ }
+ }
+ }
+
+ private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) {
+ synchronized (mInputMonitors) {
+ if (mInputMonitors.isEmpty()) return;
+ pw.println(prefix + "Gesture Monitors (implemented as spy windows):");
+ int i = 0;
+ for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) {
+ pw.append(prefix + " " + i++ + ": ").println(monitor.dump());
+ }
}
}
@@ -2621,6 +2678,7 @@
synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ }
+ synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
nativeMonitor(mPtr);
}
@@ -2689,6 +2747,11 @@
// Native callback.
private void notifyInputChannelBroken(IBinder token) {
+ synchronized (mInputMonitors) {
+ if (mInputMonitors.containsKey(token)) {
+ removeSpyWindowGestureMonitor(token);
+ }
+ }
mWindowManagerCallbacks.notifyInputChannelBroken(token);
}
@@ -2724,6 +2787,17 @@
// Native callback
private void notifyWindowUnresponsive(IBinder token, String reason) {
+ int gestureMonitorPid = -1;
+ synchronized (mInputMonitors) {
+ final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+ if (gestureMonitor != null) {
+ gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+ }
+ }
+ if (gestureMonitorPid != -1) {
+ mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
+ return;
+ }
mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
}
@@ -2734,6 +2808,17 @@
// Native callback
private void notifyWindowResponsive(IBinder token) {
+ int gestureMonitorPid = -1;
+ synchronized (mInputMonitors) {
+ final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
+ if (gestureMonitor != null) {
+ gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
+ }
+ }
+ if (gestureMonitorPid != -1) {
+ mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
+ return;
+ }
mWindowManagerCallbacks.notifyWindowResponsive(token);
}
@@ -3187,6 +3272,16 @@
* pointers such as the mouse cursor and touch spots for the given display.
*/
SurfaceControl getParentSurfaceForPointers(int displayId);
+
+ /**
+ * Create a {@link SurfaceControl} that can be configured to receive input over the entire
+ * display to implement a gesture monitor. The surface will not have a graphical buffer.
+ * @param name the name of the gesture monitor
+ * @param displayId the display to create the window in
+ * @return the SurfaceControl of the new layer container surface
+ */
+ @Nullable
+ SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
}
/**
@@ -3261,20 +3356,20 @@
* Interface for the system to handle request from InputMonitors.
*/
private final class InputMonitorHost extends IInputMonitorHost.Stub {
- private final IBinder mToken;
+ private final IBinder mInputChannelToken;
- InputMonitorHost(IBinder token) {
- mToken = token;
+ InputMonitorHost(IBinder inputChannelToken) {
+ mInputChannelToken = inputChannelToken;
}
@Override
public void pilferPointers() {
- nativePilferPointers(mPtr, mToken);
+ nativePilferPointers(mPtr, mInputChannelToken);
}
@Override
public void dispose() {
- nativeRemoveInputChannel(mPtr, mToken);
+ removeSpyWindowGestureMonitor(mInputChannelToken);
}
}
@@ -3510,6 +3605,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..9846a2b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.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.inputmethod;
+
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+
+import android.annotation.NonNull;
+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;
+
+ // Place the layer below the highest layer to place it under gesture monitors. If the surface
+ // is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface
+ // is intercepting.
+ // TODO(b/217538817): Specify the ordering in WM by usage.
+ private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1;
+
+ 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) {
+ 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
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+ mWindowHandle.visible = true;
+ mWindowHandle.focusable = false;
+ mWindowHandle.hasWallpaper = false;
+ mWindowHandle.paused = false;
+ mWindowHandle.ownerPid = Process.myPid();
+ mWindowHandle.ownerUid = Process.myUid();
+ 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's bounds */);
+
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setInputWindowInfo(mInputSurface, mWindowHandle);
+ t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
+ t.setPosition(mInputSurface, 0, 0);
+ t.setCrop(mInputSurface, null /* crop to parent surface */);
+ 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..6cb3b3b 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -107,9 +107,11 @@
@AnyThread
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported) {
+ int configChanges, boolean stylusHwSupported,
+ boolean shouldShowImeSwitcherWhenImeIsShown) {
try {
- mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+ mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
+ shouldShowImeSwitcherWhenImeIsShown);
} catch (RemoteException e) {
logRemoteException(e);
}
@@ -145,9 +147,20 @@
@AnyThread
void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
- boolean restarting) {
+ boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) {
try {
- mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+ mTarget.startInput(startInputToken, inputContext, attribute, restarting,
+ shouldShowImeSwitcherWhenImeIsShown);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ }
+ }
+
+ @AnyThread
+ void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) {
+ try {
+ mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShown);
} catch (RemoteException e) {
logRemoteException(e);
}
@@ -214,9 +227,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/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 9a53d83..80c83e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -162,28 +162,30 @@
}
@Override
- public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return Collections.emptyList();
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(
+ @UserIdInt int userId) {
return Collections.emptyList();
}
@Override
- public void onCreateInlineSuggestionsRequest(int userId,
+ public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo,
IInlineSuggestionsRequestCallback cb) {
}
@Override
- public boolean switchToInputMethod(String imeId, int userId) {
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
return false;
}
@Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+ public boolean setInputMethodEnabled(String imeId, boolean enabled,
+ @UserIdInt int userId) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index feb0d138..c207738a 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")
@@ -2312,10 +2325,12 @@
true /* direct */);
}
+ final boolean shouldShowImeSwitcherWhenImeIsShown =
+ shouldShowImeSwitcherWhenImeIsShownLocked();
final SessionState session = mCurClient.curSession;
setEnabledSessionLocked(session);
- session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
-
+ session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
+ shouldShowImeSwitcherWhenImeIsShown);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2515,7 +2530,12 @@
+ mCurTokenDisplayId);
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
- configChanges, supportStylusHw);
+ configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked());
+ }
+
+ @AnyThread
+ void scheduleResetStylusHandwriting() {
+ mHandler.obtainMessage(MSG_RESET_HANDWRITING).sendToTarget();
}
@AnyThread
@@ -2713,6 +2733,12 @@
}
@GuardedBy("ImfLock.class")
+ boolean shouldShowImeSwitcherWhenImeIsShownLocked() {
+ return shouldShowImeSwitcherLocked(
+ InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
+ }
+
+ @GuardedBy("ImfLock.class")
private boolean shouldShowImeSwitcherLocked(int visibility) {
if (!mShowOngoingImeSwitcherForPhones) return false;
if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -2972,6 +2998,7 @@
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
}
@GuardedBy("ImfLock.class")
@@ -3060,6 +3087,7 @@
}
}
+ @BinderThread
@Override
public void startStylusHandwriting(IInputMethodClient client) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
@@ -3074,18 +3102,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 +4149,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) {
@@ -4274,6 +4317,7 @@
updateImeWindowStatus(msg.arg1 == 1);
return true;
}
+
// ---------------------------------------------------------
case MSG_UNBIND_CLIENT:
@@ -4334,6 +4378,9 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
+ synchronized (ImfLock.class) {
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
+ }
return true;
case MSG_SYSTEM_UNLOCK_USER: {
final int userId = msg.arg1;
@@ -4358,21 +4405,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) {
@@ -4578,6 +4651,8 @@
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ sendShouldShowImeSwitcherWhenImeIsShownLocked();
+
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList);
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
@@ -4585,6 +4660,17 @@
}
@GuardedBy("ImfLock.class")
+ void sendShouldShowImeSwitcherWhenImeIsShownLocked() {
+ final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+ if (curMethod == null) {
+ // No need to send the data if the IME is not yet bound.
+ return;
+ }
+ curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged(
+ shouldShowImeSwitcherWhenImeIsShownLocked());
+ }
+
+ @GuardedBy("ImfLock.class")
private void updateDefaultVoiceImeIfNeededLocked() {
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
@@ -4917,28 +5003,28 @@
}
@Override
- public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return mService.getInputMethodListAsUser(userId);
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
}
@Override
- public void onCreateInlineSuggestionsRequest(int userId,
+ public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
mService.onCreateInlineSuggestionsRequest(userId, requestInfo, cb);
}
@Override
- public boolean switchToInputMethod(String imeId, int userId) {
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
return mService.switchToInputMethod(imeId, userId);
}
@Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+ public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
return mService.setInputMethodEnabled(imeId, enabled, userId);
}
@@ -5917,5 +6003,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/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 348bb2d..98bde11 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -203,6 +203,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked();
+ mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
mSwitchingDialog.show();
}
}
@@ -238,6 +239,7 @@
mSwitchingDialogTitleView = null;
mService.updateSystemUiLocked();
+ mService.sendShouldShowImeSwitcherWhenImeIsShownLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 134fb96..01aee7b 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -31,13 +31,11 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.os.BestClock;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
@@ -61,7 +59,6 @@
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
-import java.time.ZoneOffset;
import java.util.HashMap;
/**
@@ -97,15 +94,10 @@
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
PackageManagerInternal pmInternal) {
- this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(),
+ this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
new SparseArray<>());
}
- private static @NonNull Clock getDefaultClock() {
- return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
- Clock.systemUTC());
- }
-
@VisibleForTesting LocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) {
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/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index ffef803..0c3f9f0 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -829,16 +830,12 @@
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
if (request.isLocationSettingsIgnored()) {
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
@@ -933,16 +930,12 @@
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
if (request.isLocationSettingsIgnored()) {
if (!isLocationProvider) {
- mContext.enforceCallingOrSelfPermission(
- permission.WRITE_SECURE_SETTINGS,
- "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
}
@@ -1202,7 +1195,7 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "setLocationEnabledForUser", null);
- mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+ mContext.enforceCallingOrSelfPermission(WRITE_SECURE_SETTINGS, null);
LocationManager.invalidateLocalLocationEnabledCaches();
mInjector.getSettingsHelper().setLocationEnabled(enabled, userId);
@@ -1220,7 +1213,7 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "setAdasGnssLocationEnabledForUser", null);
- mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
+ LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
mInjector.getLocationSettings().updateUserSettings(userId,
settings -> settings.withAdasGnssLocationEnabled(enabled));
@@ -1239,29 +1232,29 @@
}
@Override
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public void setAutoGnssSuspended(boolean suspended) {
- mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null);
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public void setAutomotiveGnssSuspended(boolean suspended) {
+ mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null);
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
- "setAutoGnssSuspended only allowed on automotive devices");
+ "setAutomotiveGnssSuspended only allowed on automotive devices");
}
- mGnssManagerService.setAutoGnssSuspended(suspended);
+ mGnssManagerService.setAutomotiveGnssSuspended(suspended);
}
@Override
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public boolean isAutoGnssSuspended() {
- mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null);
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public boolean isAutomotiveGnssSuspended() {
+ mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null);
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
- "isAutoGnssSuspended only allowed on automotive devices");
+ "isAutomotiveGnssSuspended only allowed on automotive devices");
}
- return mGnssManagerService.isAutoGnssSuspended();
+ return mGnssManagerService.isAutomotiveGnssSuspended();
}
@Override
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index 7528f8b..be702d9 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -18,6 +18,8 @@
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.LOCATION_BYPASS;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.IntDef;
@@ -121,6 +123,29 @@
}
/**
+ * Throws a security exception if the caller does not hold the required bypass permissions.
+ */
+ public static void enforceCallingOrSelfBypassPermission(Context context) {
+ enforceBypassPermission(context, Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
+ /**
+ * Throws a security exception if the given uid/pid does not hold the required bypass
+ * perissions.
+ */
+ public static void enforceBypassPermission(Context context, int uid, int pid) {
+ if (context.checkPermission(WRITE_SECURE_SETTINGS, pid, uid) == PERMISSION_GRANTED) {
+ // TODO: disallow WRITE_SECURE_SETTINGS permission.
+ return;
+ }
+ if (context.checkPermission(LOCATION_BYPASS, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException("uid" + uid + " does not have " + LOCATION_BYPASS
+ + "or " + WRITE_SECURE_SETTINGS + ".");
+ }
+
+ /**
* Returns false if the caller does not hold the required location permissions.
*/
public static boolean checkCallingOrSelfLocationPermission(Context context,
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index b65338d..9c85d18 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -69,6 +69,14 @@
handleSetAdasGnssLocationEnabled();
return 0;
}
+ case "set-automotive-gnss-suspended": {
+ handleSetAutomotiveGnssSuspended();
+ return 0;
+ }
+ case "is-automotive-gnss-suspended": {
+ handleIsAutomotiveGnssSuspended();
+ return 0;
+ }
case "providers": {
String command = getNextArgRequired();
return parseProvidersCommand(command);
@@ -189,6 +197,24 @@
mService.setAdasGnssLocationEnabledForUser(enabled, userId);
}
+ private void handleSetAutomotiveGnssSuspended() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ throw new IllegalStateException("command only recognized on automotive devices");
+ }
+
+ boolean suspended = Boolean.parseBoolean(getNextArgRequired());
+
+ mService.setAutomotiveGnssSuspended(suspended);
+ }
+
+ private void handleIsAutomotiveGnssSuspended() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ throw new IllegalStateException("command only recognized on automotive devices");
+ }
+
+ getOutPrintWriter().println(mService.isAutomotiveGnssSuspended());
+ }
+
private void handleAddTestProvider() {
String provider = getNextArgRequired();
@@ -359,6 +385,10 @@
pw.println(" set-adas-gnss-location-enabled true|false [--user <USER_ID>]");
pw.println(" Sets the ADAS GNSS location enabled state. If no user is specified,");
pw.println(" the current user is assumed.");
+ pw.println(" is-automotive-gnss-suspended");
+ pw.println(" Gets the automotive GNSS suspended state.");
+ pw.println(" set-automotive-gnss-suspended true|false");
+ pw.println(" Sets the automotive GNSS suspended state.");
}
pw.println(" providers");
pw.println(" The providers command is followed by a subcommand, as listed below:");
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..a1ee46b 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,12 +452,20 @@
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 {
mHub.onHostEndpointConnected(info);
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "RemoteException in onHostEndpointConnected");
+ Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
}
}
@@ -446,7 +474,7 @@
try {
mHub.onHostEndpointDisconnected((char) hostEndpointId);
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "RemoteException in onHostEndpointDisconnected");
+ Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
}
}
@@ -460,6 +488,8 @@
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -471,8 +501,10 @@
try {
mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -482,8 +514,10 @@
try {
mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -493,8 +527,10 @@
try {
mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -504,8 +540,10 @@
try {
mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -514,8 +552,10 @@
try {
mHub.queryNanoapps(contextHubId);
return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
}
@@ -523,7 +563,7 @@
mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
try {
mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
- } catch (RemoteException | ServiceSpecificException e) {
+ } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
Log.e(TAG, "Exception while registering callback: " + e.getMessage());
}
}
@@ -662,8 +702,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/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index c02411e..cd2ba39 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -753,7 +753,7 @@
* Set whether the GnssLocationProvider is suspended. This method was added to help support
* power management use cases on automotive devices.
*/
- public void setAutoGnssSuspended(boolean suspended) {
+ public void setAutomotiveGnssSuspended(boolean suspended) {
synchronized (mLock) {
mAutomotiveSuspend = suspended;
}
@@ -764,7 +764,7 @@
* Return whether the GnssLocationProvider is suspended or not. This method was added to help
* support power management use cases on automotive devices.
*/
- public boolean isAutoGnssSuspended() {
+ public boolean isAutomotiveGnssSuspended() {
synchronized (mLock) {
return mAutomotiveSuspend && !mGpsEnabled;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 11fd727..0f9945c 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -113,16 +113,16 @@
* Set whether the GnssLocationProvider is suspended on the device. This method was added to
* help support power management use cases on automotive devices.
*/
- public void setAutoGnssSuspended(boolean suspended) {
- mGnssLocationProvider.setAutoGnssSuspended(suspended);
+ public void setAutomotiveGnssSuspended(boolean suspended) {
+ mGnssLocationProvider.setAutomotiveGnssSuspended(suspended);
}
/**
* Return whether the GnssLocationProvider is suspended or not. This method was added to
* help support power management use cases on automotive devices.
*/
- public boolean isAutoGnssSuspended() {
- return mGnssLocationProvider.isAutoGnssSuspended();
+ public boolean isAutomotiveGnssSuspended() {
+ return mGnssLocationProvider.isAutomotiveGnssSuspended();
}
/** Retrieve the IGpsGeofenceHardware. */
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 4d9253e..718f98a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -215,7 +215,9 @@
}
@Override
public void onPreciseCallStateChanged(PreciseCallState state) {
- if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) {
+ if (PreciseCallState.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()
+ || PreciseCallState.PRECISE_CALL_STATE_DIALING
+ == state.getForegroundCallState()) {
mActiveSubId = mSubId;
if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId);
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index 5036a6e..bfef978 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -63,16 +63,25 @@
@Override
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
- if (D) {
- Log.d(TAG, "starting gnss nmea messages");
+ if (mGnssNative.startNmeaMessageCollection()) {
+ if (D) {
+ Log.d(TAG, "starting gnss nmea messages collection");
+ }
+ return true;
+ } else {
+ Log.e(TAG, "error starting gnss nmea messages collection");
+ return false;
}
- return true;
}
@Override
protected void unregisterWithService() {
- if (D) {
- Log.d(TAG, "stopping gnss nmea messages");
+ if (mGnssNative.stopNmeaMessageCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss nmea messages collection");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss nmea messages collection");
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 936283d..0ce36d6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -43,6 +43,7 @@
private final AppOpsHelper mAppOpsHelper;
private final LocationUsageLogger mLogger;
+ private final GnssNative mGnssNative;
private boolean mIsNavigating = false;
@@ -50,6 +51,7 @@
super(injector);
mAppOpsHelper = injector.getAppOpsHelper();
mLogger = injector.getLocationUsageLogger();
+ mGnssNative = gnssNative;
gnssNative.addBaseCallbacks(this);
gnssNative.addStatusCallbacks(this);
@@ -64,16 +66,25 @@
@Override
protected boolean registerWithService(Void ignored,
Collection<GnssListenerRegistration> registrations) {
- if (D) {
- Log.d(TAG, "starting gnss status");
+ if (mGnssNative.startSvStatusCollection()) {
+ if (D) {
+ Log.d(TAG, "starting gnss sv status");
+ }
+ return true;
+ } else {
+ Log.e(TAG, "error starting gnss sv status");
+ return false;
}
- return true;
}
@Override
protected void unregisterWithService() {
- if (D) {
- Log.d(TAG, "stopping gnss status");
+ if (mGnssNative.stopSvStatusCollection()) {
+ if (D) {
+ Log.d(TAG, "stopping gnss sv status");
+ }
+ } else {
+ Log.e(TAG, "error stopping gnss sv status");
}
}
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index e072bf7..af87677 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -783,6 +783,38 @@
}
/**
+ * Starts sv status collection.
+ */
+ public boolean startSvStatusCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.startSvStatusCollection();
+ }
+
+ /**
+ * Stops sv status collection.
+ */
+ public boolean stopSvStatusCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.stopSvStatusCollection();
+ }
+
+ /**
+ * Starts NMEA message collection.
+ */
+ public boolean startNmeaMessageCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.startNmeaMessageCollection();
+ }
+
+ /**
+ * Stops NMEA message collection.
+ */
+ public boolean stopNmeaMessageCollection() {
+ Preconditions.checkState(mRegistered);
+ return mGnssHal.stopNmeaMessageCollection();
+ }
+
+ /**
* Returns true if measurement corrections are supported.
*/
public boolean isMeasurementCorrectionsSupported() {
@@ -1369,6 +1401,22 @@
return native_inject_measurement_corrections(corrections);
}
+ protected boolean startSvStatusCollection() {
+ return native_start_sv_status_collection();
+ }
+
+ protected boolean stopSvStatusCollection() {
+ return native_stop_sv_status_collection();
+ }
+
+ protected boolean startNmeaMessageCollection() {
+ return native_start_nmea_message_collection();
+ }
+
+ protected boolean stopNmeaMessageCollection() {
+ return native_stop_nmea_message_collection();
+ }
+
protected int getBatchSize() {
return native_get_batch_size();
}
@@ -1478,6 +1526,10 @@
private static native int native_read_nmea(byte[] buffer, int bufferSize);
+ private static native boolean native_start_nmea_message_collection();
+
+ private static native boolean native_stop_nmea_message_collection();
+
// location injection APIs
private static native void native_inject_location(
@@ -1501,6 +1553,11 @@
private static native void native_inject_time(long time, long timeReference, int uncertainty);
+ // sv status APIs
+ private static native boolean native_start_sv_status_collection();
+
+ private static native boolean native_stop_sv_status_collection();
+
// navigation message APIs
private static native boolean native_is_navigation_message_supported();
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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f05130..2d2edfa 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
+import static android.Manifest.permission.SET_INITIAL_LOCK;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -1650,9 +1651,13 @@
"This operation requires secure lock screen feature");
}
if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) {
- throw new SecurityException(
- "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
- + PERMISSION);
+ if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone()) {
+ // SET_INITIAL_LOCK can only be used if credential is not set.
+ } else {
+ throw new SecurityException(
+ "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or "
+ + PERMISSION);
+ }
}
final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
new file mode 100644
index 0000000..6b442a6
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.logcat;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.logcat.ILogcatManagerService;
+import android.util.Slog;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+
+/**
+ * This dialog is shown to the user before an activity in a harmful app is launched.
+ *
+ * See {@code PackageManager.setLogcatAppInfo} for more info.
+ */
+public class LogAccessConfirmationActivity extends AlertActivity implements
+ DialogInterface.OnClickListener {
+ private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName();
+
+ private String mPackageName;
+ private IntentSender mTarget;
+ private final ILogcatManagerService mLogcatManagerService =
+ ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
+
+ private int mUid;
+ private int mGid;
+ private int mPid;
+ private int mFd;
+
+ private static final String EXTRA_UID = "uid";
+ private static final String EXTRA_GID = "gid";
+ private static final String EXTRA_PID = "pid";
+ private static final String EXTRA_FD = "fd";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ mUid = intent.getIntExtra("uid", 0);
+ mGid = intent.getIntExtra("gid", 0);
+ mPid = intent.getIntExtra("pid", 0);
+ mFd = intent.getIntExtra("fd", 0);
+
+ final AlertController.AlertParams p = mAlertParams;
+ p.mTitle = getString(R.string.log_access_confirmation_title);
+ p.mView = createView();
+
+ p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny);
+ p.mNegativeButtonListener = this;
+
+ mAlert.installContent(mAlertParams);
+ }
+
+ private View createView() {
+ final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog,
+ null /*root*/);
+ ((TextView) view.findViewById(R.id.app_name_text))
+ .setText(mPackageName);
+ ((TextView) view.findViewById(R.id.message))
+ .setText(getIntent().getExtras().getString("body"));
+ return view;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ try {
+ mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ finish();
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ try {
+ mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+ }
+ finish();
+ break;
+ }
+ }
+
+ /**
+ * Create the Intent for a LogAccessConfirmationActivity.
+ */
+ public static Intent createIntent(Context context, String targetPackageName,
+ IntentSender target, int uid, int gid, int pid, int fd) {
+ final Intent intent = new Intent();
+ intent.setClass(context, LogAccessConfirmationActivity.class);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+ intent.putExtra(EXTRA_UID, uid);
+ intent.putExtra(EXTRA_GID, gid);
+ intent.putExtra(EXTRA_PID, pid);
+ intent.putExtra(EXTRA_FD, fd);
+
+ return intent;
+ }
+
+}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index ff6372ae..140c6d4 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,20 +16,36 @@
package com.android.server.logcat;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.ILogd;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.logcat.ILogcatManagerService;
import android.util.Slog;
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
- * Service responsible for manage the access to Logcat.
+ * Service responsible for managing the access to Logcat.
*/
public final class LogcatManagerService extends SystemService {
@@ -38,6 +54,43 @@
private final BinderService mBinderService;
private final ExecutorService mThreadExecutor;
private ILogd mLogdService;
+ private NotificationManager mNotificationManager;
+ private @NonNull ActivityManager mActivityManager;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
+ private static int sUidImportanceListenerCount = 0;
+ private static final int AID_SHELL_UID = 2000;
+
+ // TODO This allowlist is just a temporary workaround for the tests:
+ // FrameworksServicesTests
+ // PlatformRuleTests
+ // After adapting the test suites, the allowlist will be removed in
+ // the upcoming bug fix patches.
+ private static final String[] ALLOWABLE_TESTING_PACKAGES = {
+ "android.platform.test.rule.tests",
+ "com.android.frameworks.servicestests"
+ };
+
+ // TODO Same as the above ALLOWABLE_TESTING_PACKAGES.
+ private boolean isAllowableTestingPackage(int uid) {
+ PackageManager pm = mContext.getPackageManager();
+
+ String[] packageNames = pm.getPackagesForUid(uid);
+
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return false;
+ }
+
+ for (String name : packageNames) {
+ Slog.e(TAG, "isAllowableTestingPackage: " + name);
+
+ if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
private final class BinderService extends ILogcatManagerService.Stub {
@Override
@@ -51,6 +104,197 @@
// the logd data access is finished.
mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
}
+
+ @Override
+ public void approve(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().approve(uid, gid, pid, fd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void decline(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().decline(uid, gid, pid, fd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private ILogd getLogdService() {
+ synchronized (LogcatManagerService.this) {
+ if (mLogdService == null) {
+ LogcatManagerService.this.addLogdService();
+ }
+ return mLogdService;
+ }
+ }
+
+ private String getBodyString(Context context, String callingPackage, int uid) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ return context.getString(
+ com.android.internal.R.string.log_access_confirmation_body,
+ pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO,
+ UserHandle.getUserId(uid)).loadLabel(pm));
+ } catch (NameNotFoundException e) {
+ // App name is unknown.
+ return null;
+ }
+ }
+
+ private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid,
+ int fd) {
+
+ final ActivityManagerInternal activityManagerInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
+
+ PackageManager pm = mContext.getPackageManager();
+ String packageName = activityManagerInternal.getPackageNameByPid(pid);
+ if (packageName != null) {
+ String notificationBody = getBodyString(mContext, packageName, uid);
+
+ final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+ packageName, null, uid, gid, pid, fd);
+
+ if (notificationBody == null) {
+ // Decline the logd access if the nofitication body is unknown
+ Slog.e(TAG, "Unknown notification body, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ // TODO Next version will replace notification with dialogue
+ // per UX guidance.
+ generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody,
+ mIntent);
+ return;
+
+ }
+
+ String[] packageNames = pm.getPackagesForUid(uid);
+
+ if (ArrayUtils.isEmpty(packageNames)) {
+ // Decline the logd access if the app name is unknown
+ Slog.e(TAG, "Unknown calling package name, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ String firstPackageName = packageNames[0];
+
+ if (firstPackageName == null || firstPackageName.length() == 0) {
+ // Decline the logd access if the package name from uid is unknown
+ Slog.e(TAG, "Unknown calling package name, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ String notificationBody = getBodyString(mContext, firstPackageName, uid);
+
+ final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
+ firstPackageName, null, uid, gid, pid, fd);
+
+ if (notificationBody == null) {
+ Slog.e(TAG, "Unknown notification body, declining the logd access");
+ declineLogdAccess(uid, gid, pid, fd);
+ return;
+ }
+
+ // TODO Next version will replace notification with dialogue
+ // per UX guidance.
+ generateNotificationWithBodyContent(notificationId, clientInfo,
+ notificationBody, mIntent);
+ }
+
+ private void declineLogdAccess(int uid, int gid, int pid, int fd) {
+ try {
+ getLogdService().decline(uid, gid, pid, fd);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Fails to call remote functions ", ex);
+ }
+ }
+
+ private void generateNotificationWithBodyContent(int notificationId, String clientInfo,
+ String notificationBody, Intent intent) {
+ final Notification.Builder notificationBuilder = new Notification.Builder(
+ mContext,
+ SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setIdentifier(String.valueOf(notificationId) + clientInfo);
+ intent.putExtra("body", notificationBody);
+
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_info)
+ .setContentTitle(
+ mContext.getString(R.string.log_access_confirmation_title))
+ .setContentText(notificationBody)
+ .setContentIntent(
+ PendingIntent.getActivity(mContext, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE))
+ .setTicker(mContext.getString(R.string.log_access_confirmation_title))
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(true);
+ mNotificationManager.notify(notificationId, notificationBuilder.build());
+ }
+
+ /**
+ * A class which watches an uid for background access and notifies the logdMonitor when
+ * the package status becomes foreground (importance change)
+ */
+ private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+ private final int mExpectedUid;
+ private final int mExpectedGid;
+ private final int mExpectedPid;
+ private final int mExpectedFd;
+ private int mExpectedImportance;
+ private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+
+ UidImportanceListener(int uid, int gid, int pid, int fd, int importance) {
+ mExpectedUid = uid;
+ mExpectedGid = gid;
+ mExpectedPid = pid;
+ mExpectedFd = fd;
+ mExpectedImportance = importance;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (uid == mExpectedUid) {
+ mCurrentImportance = importance;
+
+ /**
+ * 1) If the process status changes to foreground, send a notification
+ * for user consent.
+ * 2) If the process status remains background, we decline logd access request.
+ **/
+ if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+ String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+ sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid,
+ mExpectedFd);
+ mActivityManager.removeOnUidImportanceListener(this);
+
+ synchronized (LogcatManagerService.this) {
+ sUidImportanceListenerCount--;
+ }
+ } else {
+ try {
+ getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Fails to call remote functions ", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private static String getClientInfo(int uid, int gid, int pid, int fd) {
+ return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
+ + Integer.toString(pid) + " FD=" + Integer.toString(fd);
}
private class LogdMonitor implements Runnable {
@@ -74,9 +318,7 @@
}
/**
- * The current version grant the permission by default.
- * And track the logd access.
- * The next version will generate a prompt for users.
+ * LogdMonitor generates a prompt for users.
* The users decide whether the logd access is allowed.
*/
@Override
@@ -86,10 +328,61 @@
}
if (mStart) {
- try {
- mLogdService.approve(mUid, mGid, mPid, mFd);
- } catch (RemoteException ex) {
- Slog.e(TAG, "Fails to call remote functions ", ex);
+
+ // TODO See the comments of ALLOWABLE_TESTING_PACKAGES.
+ if (isAllowableTestingPackage(mUid)) {
+ try {
+ getLogdService().approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ // If the access request is coming from adb shell, approve the logd access
+ if (mUid == AID_SHELL_UID) {
+ try {
+ getLogdService().approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ final int procState = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(mUid);
+ // If the process is foreground, send a notification for user consent
+ if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ String clientInfo = getClientInfo(mUid, mGid, mPid, mFd);
+ sendNotification(0, clientInfo, mUid, mGid, mPid, mFd);
+ } else {
+ /**
+ * If the process is background, add a background process change listener and
+ * monitor if the process status changes.
+ * To avoid clients registering multiple listeners, we limit the number of
+ * maximum listeners to MAX_UID_IMPORTANCE_COUNT_LISTENER.
+ **/
+ if (mActivityManager == null) {
+ return;
+ }
+
+ synchronized (LogcatManagerService.this) {
+ if (sUidImportanceListenerCount < MAX_UID_IMPORTANCE_COUNT_LISTENER) {
+ // Trigger addOnUidImportanceListener when there is an update from
+ // the importance of the process
+ mActivityManager.addOnUidImportanceListener(new UidImportanceListener(
+ mUid, mGid, mPid, mFd,
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE),
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+ sUidImportanceListenerCount++;
+ } else {
+ try {
+ getLogdService().decline(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
}
}
}
@@ -100,6 +393,8 @@
mContext = context;
mBinderService = new BinderService();
mThreadExecutor = Executors.newCachedThreadPool();
+ mActivityManager = context.getSystemService(ActivityManager.class);
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
}
@Override
@@ -114,5 +409,4 @@
private void addLogdService() {
mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
}
-
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 91de9e5..728782c 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -45,7 +45,6 @@
import com.android.internal.R;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -448,15 +447,16 @@
case BluetoothProfile.A2DP:
mA2dpProfile = (BluetoothA2dp) proxy;
// It may contain null.
- activeDevices = Collections.singletonList(mA2dpProfile.getActiveDevice());
+ activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP);
break;
case BluetoothProfile.HEARING_AID:
mHearingAidProfile = (BluetoothHearingAid) proxy;
- activeDevices = mHearingAidProfile.getActiveDevices();
+ activeDevices = mBluetoothAdapter.getActiveDevices(
+ BluetoothProfile.HEARING_AID);
break;
case BluetoothProfile.LE_AUDIO:
mLeAudioProfile = (BluetoothLeAudio) proxy;
- activeDevices = mLeAudioProfile.getActiveDevices();
+ activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
break;
default:
return;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 303ab46..7f997df 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,9 +677,9 @@
UserRecord userRecord = routerRecord.mUserRecord;
userRecord.mRouterRecords.remove(routerRecord);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
- routerRecord.mPackageName, /* preferredFeatures=*/ null));
+ routerRecord.mPackageName, null));
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
userRecord.mHandler));
@@ -694,10 +694,10 @@
}
routerRecord.mDiscoveryPreference = discoveryRequest;
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
routerRecord.mUserRecord.mHandler,
routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures()));
+ routerRecord.mDiscoveryPreference));
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
routerRecord.mUserRecord.mHandler));
@@ -921,7 +921,7 @@
// TODO: UserRecord <-> routerRecord, why do they reference each other?
// How about removing mUserRecord from routerRecord?
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager,
+ obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
@@ -2118,19 +2118,19 @@
}
}
- private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord,
+ private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
@NonNull IMediaRouter2Manager manager) {
try {
- manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName,
- routerRecord.mDiscoveryPreference.getPreferredFeatures());
+ manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
+ routerRecord.mDiscoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);
}
}
- private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName,
- @Nullable List<String> preferredFeatures) {
+ private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
+ @Nullable RouteDiscoveryPreference discoveryPreference) {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
@@ -2143,7 +2143,8 @@
}
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures);
+ manager.notifyDiscoveryPreferenceChanged(routerPackageName,
+ discoveryPreference);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify preferred features changed."
+ " Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fac7a40..8ce67a6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -98,8 +98,6 @@
import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.MATCH_WIFI;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
@@ -168,7 +166,6 @@
import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkIdentity;
@@ -1324,7 +1321,7 @@
};
/**
- * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
+ * Check {@link NetworkPolicy} against current {@link NetworkStatsManager}
* to show visible notifications as needed.
*/
@GuardedBy("mNetworkPoliciesSecondLock")
@@ -2322,6 +2319,18 @@
}
/**
+ * Template to match all metered carrier networks with the given IMSI.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+ Objects.requireNonNull(subscriberId);
+ return new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(subscriberId))
+ .setMeteredness(METERED_YES).build();
+ }
+
+ /**
* Update the given {@link NetworkPolicy} based on any carrier-provided
* defaults via {@link SubscriptionPlan} or {@link CarrierConfigManager}.
* Leaves policy untouched if the user has modified it.
@@ -2724,7 +2733,8 @@
out.startTag(null, TAG_NETWORK_POLICY);
writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
- final String subscriberId = template.getSubscriberId();
+ final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+ : template.getSubscriberIds().iterator().next();
if (subscriberId != null) {
out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
}
@@ -3101,7 +3111,7 @@
}
// When two normalized templates conflict, prefer the most
// restrictive policy
- policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds);
+ policy.template = normalizeTemplate(policy.template, mMergedSubscriberIds);
final NetworkPolicy existing = mNetworkPolicy.get(policy.template);
if (existing == null || existing.compareTo(policy) > 0) {
if (existing != null) {
@@ -3112,6 +3122,46 @@
}
}
+ /**
+ * Examine the given template and normalize it.
+ * We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ *
+ * There can be multiple merged subscriberIds for multi-SIM devices.
+ *
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ */
+ private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
+ @NonNull List<String[]> mergedList) {
+ // Now there are several types of network which uses Subscriber Id to store network
+ // information. For instance:
+ // 1. A merged carrier wifi network which has TYPE_WIFI with a Subscriber Id.
+ // 2. A typical cellular network could have TYPE_MOBILE with a Subscriber Id.
+
+ if (template.getSubscriberIds().isEmpty()) return template;
+
+ for (final String[] merged : mergedList) {
+ // TODO: Handle incompatible subscriberIds if that happens in practice.
+ for (final String subscriberId : template.getSubscriberIds()) {
+ if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) {
+ // Requested template subscriber is part of the merged group; return
+ // a template that matches all merged subscribers.
+ return new NetworkTemplate.Builder(template.getMatchRule())
+ .setWifiNetworkKeys(template.getWifiNetworkKeys())
+ .setSubscriberIds(Set.of(merged))
+ .setMeteredness(template.getMeteredness())
+ .build();
+ }
+ }
+ }
+
+ return template;
+ }
+
@Override
public void snoozeLimit(NetworkTemplate template) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
@@ -5559,7 +5609,11 @@
NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
NetworkTemplate templateCarrier = subscriber != null
? buildTemplateCarrierMetered(subscriber) : null;
- NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber);
+ NetworkTemplate templateMobile = subscriber != null
+ ? new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setSubscriberIds(Set.of(subscriber))
+ .setMeteredness(android.net.NetworkStats.METERED_YES)
+ .build() : null;
for (NetworkPolicy policy : policies) {
// All policies loaded from disk will be carrier templates, and setting will also only
// set carrier templates, but we clear mobile templates just in case one is set by
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 583cdd5..647a89e 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -104,6 +104,12 @@
return -1 * Boolean.compare(leftPeople, rightPeople);
}
+ boolean leftSystemMax = isSystemMax(left);
+ boolean rightSystemMax = isSystemMax(right);
+ if (leftSystemMax != rightSystemMax) {
+ return -1 * Boolean.compare(leftSystemMax, rightSystemMax);
+ }
+
if (leftImportance != rightImportance) {
// by importance, high to low
return -1 * Integer.compare(leftImportance, rightImportance);
@@ -173,6 +179,20 @@
return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance());
}
+ protected boolean isSystemMax(NotificationRecord record) {
+ if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+ return false;
+ }
+ String packageName = record.getSbn().getPackageName();
+ if ("android".equals(packageName)) {
+ return true;
+ }
+ if ("com.android.systemui".equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean isOngoing(NotificationRecord record) {
final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
return (record.getNotification().flags & ongoingFlags) != 0;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 96d7521..70e968f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -201,6 +201,10 @@
private boolean mIsAppImportanceLocked;
private ArraySet<Uri> mGrantableUris;
+ // Storage for phone numbers that were found to be associated with
+ // contacts in this notification.
+ private ArraySet<String> mPhoneNumbers;
+
// Whether this notification record should have an update logged the next time notifications
// are sorted.
private boolean mPendingLogUpdate = false;
@@ -1547,6 +1551,26 @@
return mPendingLogUpdate;
}
+ /**
+ * Merge the given set of phone numbers into the list of phone numbers that
+ * are cached on this notification record.
+ */
+ public void mergePhoneNumbers(ArraySet<String> phoneNumbers) {
+ // if the given phone numbers are null or empty then don't do anything
+ if (phoneNumbers == null || phoneNumbers.size() == 0) {
+ return;
+ }
+ // initialize if not already
+ if (mPhoneNumbers == null) {
+ mPhoneNumbers = new ArraySet<>();
+ }
+ mPhoneNumbers.addAll(phoneNumbers);
+ }
+
+ public ArraySet<String> getPhoneNumbers() {
+ return mPhoneNumbers;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 0cbdbc1..5d18069 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -19,7 +19,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
import android.annotation.NonNull;
@@ -77,7 +77,8 @@
assertFlag();
final long callingId = Binder.clearCallingIdentity();
try {
- return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+ return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid)
+ == PERMISSION_GRANTED;
} finally {
Binder.restoreCallingIdentity(callingId);
}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index d7bc3bb..dc4d04f 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -68,7 +68,10 @@
private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
"validate_notification_people_enabled";
- private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
+ private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY,
+ Contacts.STARRED, Contacts.HAS_PHONE_NUMBER };
+ private static final String[] PHONE_LOOKUP_PROJECTION =
+ { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER };
private static final int MAX_PEOPLE = 10;
private static final int PEOPLE_CACHE_SIZE = 200;
@@ -409,6 +412,35 @@
return lookupResult;
}
+ @VisibleForTesting
+ // Performs a contacts search using searchContacts, and then follows up by looking up
+ // any phone numbers associated with the resulting contact information and merge those
+ // into the lookup result as well. Will have no additional effect if the contact does
+ // not have any phone numbers.
+ LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
+ LookupResult lookupResult = searchContacts(context, lookupUri);
+ String phoneLookupKey = lookupResult.getPhoneLookupKey();
+ if (phoneLookupKey != null) {
+ String selection = Contacts.LOOKUP_KEY + " = ?";
+ String[] selectionArgs = new String[] { phoneLookupKey };
+ try (Cursor cursor = context.getContentResolver().query(
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
+ selection, selectionArgs, /* sortOrder= */ null)) {
+ if (cursor == null) {
+ Slog.w(TAG, "Cursor is null when querying contact phone number.");
+ return lookupResult;
+ }
+
+ while (cursor.moveToNext()) {
+ lookupResult.mergePhoneNumber(cursor);
+ }
+ } catch (Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
+ }
+ }
+ return lookupResult;
+ }
+
private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
final int workUserId = findWorkUserId(context);
if (workUserId == -1) {
@@ -454,6 +486,9 @@
private final long mExpireMillis;
private float mAffinity = NONE;
+ private boolean mHasPhone = false;
+ private String mPhoneLookupKey = null;
+ private ArraySet<String> mPhoneNumbers = new ArraySet<>();
public LookupResult() {
mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
@@ -473,6 +508,15 @@
Slog.i(TAG, "invalid cursor: no _ID");
}
+ // Lookup key for potentially looking up contact phone number later
+ final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY);
+ if (lookupKeyIdx >= 0) {
+ mPhoneLookupKey = cursor.getString(lookupKeyIdx);
+ if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey);
+ } else {
+ if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY");
+ }
+
// Starred
final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
if (starIdx >= 0) {
@@ -484,6 +528,39 @@
} else {
if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
}
+
+ // whether a phone number is present
+ final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER);
+ if (hasPhoneIdx >= 0) {
+ mHasPhone = cursor.getInt(hasPhoneIdx) != 0;
+ if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone);
+ } else {
+ if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER");
+ }
+ }
+
+ // Returns the phone lookup key that is cached in this result, or null
+ // if the contact has no known phone info.
+ public String getPhoneLookupKey() {
+ if (!mHasPhone) {
+ return null;
+ }
+ return mPhoneLookupKey;
+ }
+
+ // Merge phone number found in this lookup and store it in mPhoneNumbers.
+ public void mergePhoneNumber(Cursor cursor) {
+ final int phoneNumIdx = cursor.getColumnIndex(
+ ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
+ if (phoneNumIdx >= 0) {
+ mPhoneNumbers.add(cursor.getString(phoneNumIdx));
+ } else {
+ if (DEBUG) Slog.d(TAG, "invalid cursor: no NORMALIZED_NUMBER");
+ }
+ }
+
+ public ArraySet<String> getPhoneNumbers() {
+ return mPhoneNumbers;
}
private boolean isExpired() {
@@ -509,6 +586,7 @@
// Amount of time to wait for a result from the contacts db before rechecking affinity.
private static final long LOOKUP_TIME = 1000;
private float mContactAffinity = NONE;
+ private ArraySet<String> mPhoneNumbers = null;
private NotificationRecord mRecord;
private PeopleRankingReconsideration(Context context, String key,
@@ -543,7 +621,9 @@
lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
} else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
- lookupResult = searchContacts(mContext, uri);
+ // only look up phone number if this is a contact lookup uri and thus isn't
+ // already directly a phone number.
+ lookupResult = searchContactsAndLookupNumbers(mContext, uri);
} else {
lookupResult = new LookupResult(); // invalid person for the cache
if (!"name".equals(uri.getScheme())) {
@@ -561,6 +641,13 @@
Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
}
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
+ // merge any phone numbers found in this lookup result
+ if (lookupResult.getPhoneNumbers() != null) {
+ if (mPhoneNumbers == null) {
+ mPhoneNumbers = new ArraySet<>();
+ }
+ mPhoneNumbers.addAll(lookupResult.getPhoneNumbers());
+ }
} else {
if (DEBUG) Slog.d(TAG, "lookupResult is null");
}
@@ -581,6 +668,7 @@
float affinityBound = operand.getContactAffinity();
operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
+ operand.mergePhoneNumbers(mPhoneNumbers);
}
public float getContactAffinity() {
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b186f61..d04b331 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -24,18 +24,24 @@
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioAttributes;
+import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.NotificationMessagingUtil;
import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.Date;
public class ZenModeFiltering {
@@ -64,13 +70,22 @@
pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
pw.println(REPEAT_CALLERS.mThresholdMinutes);
synchronized (REPEAT_CALLERS) {
- if (!REPEAT_CALLERS.mCalls.isEmpty()) {
- pw.print(prefix); pw.println("RepeatCallers.mCalls=");
- for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+ if (!REPEAT_CALLERS.mTelCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mTelCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mTelCalls.size(); i++) {
pw.print(prefix); pw.print(" ");
- pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+ pw.print(REPEAT_CALLERS.mTelCalls.keyAt(i));
pw.print(" at ");
- pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+ pw.println(ts(REPEAT_CALLERS.mTelCalls.valueAt(i)));
+ }
+ }
+ if (!REPEAT_CALLERS.mOtherCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mOtherCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mOtherCalls.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(REPEAT_CALLERS.mOtherCalls.keyAt(i));
+ pw.print(" at ");
+ pw.println(ts(REPEAT_CALLERS.mOtherCalls.valueAt(i)));
}
}
}
@@ -126,7 +141,7 @@
}
protected void recordCall(NotificationRecord record) {
- REPEAT_CALLERS.recordCall(mContext, extras(record));
+ REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers());
}
/**
@@ -330,34 +345,40 @@
}
private static class RepeatCallers {
- // Person : time
- private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+ // We keep a separate map per uri scheme to do more generous number-matching
+ // handling on telephone numbers specifically. For other inputs, we
+ // simply match directly on the string.
+ private final ArrayMap<String, Long> mTelCalls = new ArrayMap<>();
+ private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>();
private int mThresholdMinutes;
- private synchronized void recordCall(Context context, Bundle extras) {
+ private synchronized void recordCall(Context context, Bundle extras,
+ ArraySet<String> phoneNumbers) {
setThresholdMinutes(context);
if (mThresholdMinutes <= 0 || extras == null) return;
- final String peopleString = peopleString(extras);
- if (peopleString == null) return;
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return;
final long now = System.currentTimeMillis();
- cleanUp(mCalls, now);
- mCalls.put(peopleString, now);
+ cleanUp(mTelCalls, now);
+ cleanUp(mOtherCalls, now);
+ recordCallers(extraPeople, phoneNumbers, now);
}
private synchronized boolean isRepeat(Context context, Bundle extras) {
setThresholdMinutes(context);
if (mThresholdMinutes <= 0 || extras == null) return false;
- final String peopleString = peopleString(extras);
- if (peopleString == null) return false;
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return false;
final long now = System.currentTimeMillis();
- cleanUp(mCalls, now);
- return mCalls.containsKey(peopleString);
+ cleanUp(mTelCalls, now);
+ cleanUp(mOtherCalls, now);
+ return checkCallers(context, extraPeople);
}
private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
final int N = calls.size();
for (int i = N - 1; i >= 0; i--) {
- final long time = mCalls.valueAt(i);
+ final long time = calls.valueAt(i);
if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
calls.removeAt(i);
}
@@ -367,10 +388,16 @@
// Clean up all calls that occurred after the given time.
// Used only for tests, to clean up after testing.
private synchronized void cleanUpCallsAfter(long timeThreshold) {
- for (int i = mCalls.size() - 1; i >= 0; i--) {
- final long time = mCalls.valueAt(i);
+ for (int i = mTelCalls.size() - 1; i >= 0; i--) {
+ final long time = mTelCalls.valueAt(i);
if (time > timeThreshold) {
- mCalls.removeAt(i);
+ mTelCalls.removeAt(i);
+ }
+ }
+ for (int j = mOtherCalls.size() - 1; j >= 0; j--) {
+ final long time = mOtherCalls.valueAt(j);
+ if (time > timeThreshold) {
+ mOtherCalls.removeAt(j);
}
}
}
@@ -382,21 +409,74 @@
}
}
- private static String peopleString(Bundle extras) {
- final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
- if (extraPeople == null || extraPeople.length == 0) return null;
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < extraPeople.length; i++) {
- String extraPerson = extraPeople[i];
- if (extraPerson == null) continue;
- extraPerson = extraPerson.trim();
- if (extraPerson.isEmpty()) continue;
- if (sb.length() > 0) {
- sb.append('|');
+ private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
+ long now) {
+ for (int i = 0; i < people.length; i++) {
+ String person = people[i];
+ if (person == null) continue;
+ final Uri uri = Uri.parse(person);
+ if ("tel".equals(uri.getScheme())) {
+ String tel = uri.getSchemeSpecificPart();
+ // while ideally we should not need to do this, sometimes we have seen tel
+ // numbers given in a url-encoded format
+ try {
+ tel = URLDecoder.decode(tel, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore, keep the original tel string
+ Slog.w(TAG, "unsupported encoding in tel: uri input");
+ }
+ mTelCalls.put(tel, now);
+ } else {
+ // for non-tel calls, store the entire string, uri-component and all
+ mOtherCalls.put(person, now);
}
- sb.append(extraPerson);
}
- return sb.length() == 0 ? null : sb.toString();
+
+ // record any additional numbers from the notification record if
+ // provided; these are in the format of just a phone number string
+ if (phoneNumbers != null) {
+ for (String num : phoneNumbers) {
+ mTelCalls.put(num, now);
+ }
+ }
+ }
+
+ private synchronized boolean checkCallers(Context context, String[] people) {
+ // get the default country code for checking telephone numbers
+ final String defaultCountryCode =
+ context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
+ for (int i = 0; i < people.length; i++) {
+ String person = people[i];
+ if (person == null) continue;
+ final Uri uri = Uri.parse(person);
+ if ("tel".equals(uri.getScheme())) {
+ String number = uri.getSchemeSpecificPart();
+ if (mTelCalls.containsKey(number)) {
+ // check directly via map first
+ return true;
+ } else {
+ // see if a number that matches via areSameNumber exists
+ String numberToCheck = number;
+ try {
+ numberToCheck = URLDecoder.decode(number, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore, continue to use the original string
+ Slog.w(TAG, "unsupported encoding in tel: uri input");
+ }
+ for (String prev : mTelCalls.keySet()) {
+ if (PhoneNumberUtils.areSamePhoneNumber(
+ numberToCheck, prev, defaultCountryCode)) {
+ return true;
+ }
+ }
+ }
+ } else {
+ if (mOtherCalls.containsKey(person)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2e9ad50..2d87099 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,9 +32,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.component.ParsedApexSystemService;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Binder;
@@ -59,6 +56,9 @@
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.component.ParsedApexSystemService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.TimingsTraceAndSlog;
import com.google.android.collect.Lists;
@@ -414,9 +414,11 @@
throws PackageManagerException;
/**
- * Get a map of system services defined in an apex mapped to the jar files they reside in.
+ * Get a list of apex system services implemented in an apex.
+ *
+ * <p>The list is sorted by initOrder for consistency.
*/
- public abstract Map<String, String> getApexSystemServices();
+ public abstract List<ApexSystemServiceInfo> getApexSystemServices();
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -449,7 +451,7 @@
* Map of all apex system services to the jar files they are contained in.
*/
@GuardedBy("mLock")
- private Map<String, String> mApexSystemServices = new ArrayMap<>();
+ private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
@@ -605,14 +607,19 @@
}
String name = service.getName();
- if (mApexSystemServices.containsKey(name)) {
- throw new IllegalStateException(String.format(
- "Duplicate apex-system-service %s from %s, %s",
- name, mApexSystemServices.get(name), service.getJarPath()));
+ for (ApexSystemServiceInfo info : mApexSystemServices) {
+ if (info.getName().equals(name)) {
+ throw new IllegalStateException(String.format(
+ "Duplicate apex-system-service %s from %s, %s",
+ name, info.mJarPath, service.getJarPath()));
+ }
}
- mApexSystemServices.put(name, service.getJarPath());
+ ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+ service.getName(), service.getJarPath(), service.getInitOrder());
+ mApexSystemServices.add(info);
}
+ Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1133,7 +1140,7 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
synchronized (mLock) {
Preconditions.checkState(mApexSystemServices != null,
"APEX packages have not been scanned");
@@ -1423,10 +1430,10 @@
}
@Override
- public Map<String, String> getApexSystemServices() {
+ public List<ApexSystemServiceInfo> getApexSystemServices() {
// TODO(satayev): we can't really support flattened apex use case, and need to migrate
// the manifest entries into system's manifest asap.
- return Collections.emptyMap();
+ return Collections.emptyList();
}
@Override
diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
new file mode 100644
index 0000000..f75ba6d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+
+/**
+ * A helper class that contains information about apex-system-service to be used within system
+ * server process.
+ */
+public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
+
+ final String mName;
+ @Nullable
+ final String mJarPath;
+ final int mInitOrder;
+
+ public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
+ this.mName = name;
+ this.mJarPath = jarPath;
+ this.mInitOrder = initOrder;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getJarPath() {
+ return mJarPath;
+ }
+
+ public int getInitOrder() {
+ return mInitOrder;
+ }
+
+ @Override
+ public int compareTo(ApexSystemServiceInfo other) {
+ if (mInitOrder == other.mInitOrder) {
+ return mName.compareTo(other.mName);
+ }
+ // higher initOrder values take precedence
+ return -Integer.compare(mInitOrder, other.mInitOrder);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 4b999e9..d745a23 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -48,6 +48,7 @@
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SELinuxUtil;
import dalvik.system.VMRuntime;
@@ -277,8 +278,8 @@
});
}
- public void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,
- int userId, int flags) {
+ public void prepareAppDataContentsLIF(AndroidPackage pkg,
+ @Nullable PackageStateInternal pkgSetting, int userId, int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
@@ -287,7 +288,7 @@
}
private void prepareAppDataContentsLeafLIF(AndroidPackage pkg,
- @Nullable PackageSetting pkgSetting, int userId, int flags) {
+ @Nullable PackageStateInternal pkgSetting, int userId, int flags) {
final String volumeUuid = pkg.getVolumeUuid();
final String packageName = pkg.getPackageName();
@@ -548,6 +549,10 @@
}
public void migrateKeyStoreData(int previousAppId, int appId) {
+ // If previous UID is system UID, declaring inheritKeyStoreKeys is not supported.
+ // Silently ignore the request to migrate keys.
+ if (previousAppId == Process.SYSTEM_UID) return;
+
for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) {
int srcUid = UserHandle.getUid(userId, previousAppId);
int destUid = UserHandle.getUid(userId, appId);
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index b916de3..5b2e097 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -1237,21 +1237,25 @@
// NOTE: this must come after all removals from data structures but before we update the
// cache
if (setting.getSharedUser() != null) {
- for (int i = setting.getSharedUser().packages.size() - 1; i >= 0; i--) {
- if (setting.getSharedUser().packages.valueAt(i) == setting) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ setting.getSharedUser().getPackageStates();
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ if (sharedUserPackages.valueAt(i) == setting) {
continue;
}
addPackageInternal(
- setting.getSharedUser().packages.valueAt(i), settings);
+ sharedUserPackages.valueAt(i), settings);
}
}
synchronized (mCacheLock) {
removeAppIdFromVisibilityCache(setting.getAppId());
if (mShouldFilterCache != null && setting.getSharedUser() != null) {
- for (int i = setting.getSharedUser().packages.size() - 1; i >= 0; i--) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ setting.getSharedUser().getPackageStates();
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
PackageStateInternal siblingSetting =
- setting.getSharedUser().packages.valueAt(i);
+ sharedUserPackages.valueAt(i);
if (siblingSetting == setting) {
continue;
}
@@ -1368,8 +1372,8 @@
callingSharedPkgSettings = null;
} else {
callingPkgSetting = null;
- callingSharedPkgSettings =
- ((PackageStateInternal) callingSetting).getSharedUser().packages;
+ callingSharedPkgSettings = ((PackageStateInternal) callingSetting)
+ .getSharedUser().getPackageStates();
}
} else {
callingPkgSetting = null;
diff --git a/services/core/java/com/android/server/pm/ChangedPackagesTracker.java b/services/core/java/com/android/server/pm/ChangedPackagesTracker.java
new file mode 100644
index 0000000..bd12981
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ChangedPackagesTracker.java
@@ -0,0 +1,114 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ChangedPackages;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+class ChangedPackagesTracker {
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private int mChangedPackagesSequenceNumber;
+ /**
+ * List of changed [installed, removed or updated] packages.
+ * mapping from user id -> sequence number -> package name
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<SparseArray<String>> mUserIdToSequenceToPackage = new SparseArray<>();
+ /**
+ * The sequence number of the last change to a package.
+ * mapping from user id -> package name -> sequence number
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers =
+ new SparseArray<>();
+
+ @Nullable
+ public ChangedPackages getChangedPackages(int sequenceNumber, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (sequenceNumber >= mChangedPackagesSequenceNumber) {
+ return null;
+ }
+ final SparseArray<String> changedPackages = mUserIdToSequenceToPackage.get(userId);
+ if (changedPackages == null) {
+ return null;
+ }
+ final List<String> packageNames =
+ new ArrayList<>(mChangedPackagesSequenceNumber - sequenceNumber);
+ for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
+ final String packageName = changedPackages.get(i);
+ if (packageName != null) {
+ packageNames.add(packageName);
+ }
+ }
+ return packageNames.isEmpty()
+ ? null : new ChangedPackages(mChangedPackagesSequenceNumber, packageNames);
+ }
+ }
+
+ int getSequenceNumber() {
+ return mChangedPackagesSequenceNumber;
+ }
+
+ void iterateAll(@NonNull BiConsumer<Integer, SparseArray<SparseArray<String>>>
+ sequenceNumberAndValues) {
+ synchronized (mLock) {
+ sequenceNumberAndValues.accept(mChangedPackagesSequenceNumber,
+ mUserIdToSequenceToPackage);
+ }
+ }
+
+ void updateSequenceNumber(@NonNull String packageName, int[] userList) {
+ for (int i = userList.length - 1; i >= 0; --i) {
+ final int userId = userList[i];
+ SparseArray<String> changedPackages = mUserIdToSequenceToPackage.get(userId);
+ if (changedPackages == null) {
+ changedPackages = new SparseArray<>();
+ mUserIdToSequenceToPackage.put(userId, changedPackages);
+ }
+ Map<String, Integer> sequenceNumbers = mChangedPackagesSequenceNumbers.get(userId);
+ if (sequenceNumbers == null) {
+ sequenceNumbers = new HashMap<>();
+ mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
+ }
+ final Integer sequenceNumber = sequenceNumbers.get(packageName);
+ if (sequenceNumber != null) {
+ changedPackages.remove(sequenceNumber);
+ }
+ changedPackages.put(mChangedPackagesSequenceNumber, packageName);
+ sequenceNumbers.put(packageName, mChangedPackagesSequenceNumber);
+ }
+ mChangedPackagesSequenceNumber++;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index ba003d2..aa393d2 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -616,98 +616,113 @@
}
void dumpActivityResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
- : "Activity Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
+ : "Activity Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpProviderResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
- : "Provider Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
+ : "Provider Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpReceiverResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
- : "Receiver Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
+ : "Receiver Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpServiceResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
- : "Service Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
+ : "Service Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpContentProviders(PrintWriter pw, DumpState dumpState, String packageName) {
- boolean printedSomething = false;
- for (ParsedProvider p : mProviders.mProviders.values()) {
- if (packageName != null && !packageName.equals(p.getPackageName())) {
- continue;
- }
- if (!printedSomething) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
+ synchronized (mLock) {
+ boolean printedSomething = false;
+ for (ParsedProvider p : mProviders.mProviders.values()) {
+ if (packageName != null && !packageName.equals(p.getPackageName())) {
+ continue;
}
- pw.println("Registered ContentProviders:");
- printedSomething = true;
- }
- pw.print(" ");
- ComponentName.printShortString(pw, p.getPackageName(), p.getName());
- pw.println(":");
- pw.print(" ");
- pw.println(p.toString());
- }
- printedSomething = false;
- for (Map.Entry<String, ParsedProvider> entry :
- mProvidersByAuthority.entrySet()) {
- ParsedProvider p = entry.getValue();
- if (packageName != null && !packageName.equals(p.getPackageName())) {
- continue;
- }
- if (!printedSomething) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Registered ContentProviders:");
+ printedSomething = true;
}
- pw.println("ContentProvider Authorities:");
- printedSomething = true;
+ pw.print(" ");
+ ComponentName.printShortString(pw, p.getPackageName(), p.getName());
+ pw.println(":");
+ pw.print(" ");
+ pw.println(p.toString());
}
- pw.print(" ["); pw.print(entry.getKey()); pw.println("]:");
- pw.print(" "); pw.println(p.toString());
+ printedSomething = false;
+ for (Map.Entry<String, ParsedProvider> entry :
+ mProvidersByAuthority.entrySet()) {
+ ParsedProvider p = entry.getValue();
+ if (packageName != null && !packageName.equals(p.getPackageName())) {
+ continue;
+ }
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("ContentProvider Authorities:");
+ printedSomething = true;
+ }
+ pw.print(" [");
+ pw.print(entry.getKey());
+ pw.println("]:");
+ pw.print(" ");
+ pw.println(p.toString());
- AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
+ AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
- if (pkg != null) {
- pw.print(" applicationInfo=");
- pw.println(AndroidPackageUtils.generateAppInfoWithoutState(pkg));
+ if (pkg != null) {
+ pw.print(" applicationInfo=");
+ pw.println(AndroidPackageUtils.generateAppInfoWithoutState(pkg));
+ }
}
}
}
void dumpServicePermissions(PrintWriter pw, DumpState dumpState) {
- if (dumpState.onTitlePrinted()) pw.println();
- pw.println("Service permissions:");
+ synchronized (mLock) {
+ if (dumpState.onTitlePrinted()) pw.println();
+ pw.println("Service permissions:");
- final Iterator<Pair<ParsedService, ParsedIntentInfo>> filterIterator =
- mServices.filterIterator();
- while (filterIterator.hasNext()) {
- final Pair<ParsedService, ParsedIntentInfo> pair = filterIterator.next();
- ParsedService service = pair.first;
+ final Iterator<Pair<ParsedService, ParsedIntentInfo>> filterIterator =
+ mServices.filterIterator();
+ while (filterIterator.hasNext()) {
+ final Pair<ParsedService, ParsedIntentInfo> pair = filterIterator.next();
+ ParsedService service = pair.first;
- final String permission = service.getPermission();
- if (permission != null) {
- pw.print(" ");
- pw.print(service.getComponentName().flattenToShortString());
- pw.print(": ");
- pw.println(permission);
+ final String permission = service.getPermission();
+ if (permission != null) {
+ pw.print(" ");
+ pw.print(service.getComponentName().flattenToShortString());
+ pw.print(": ");
+ pw.println(permission);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index fcf4a02..0ae3418 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -43,12 +43,16 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -214,7 +218,7 @@
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
String resolveExternalPackageName(AndroidPackage pkg);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
- String resolveInternalPackageNameLPr(String packageName, long versionCode);
+ String resolveInternalPackageName(String packageName, long versionCode);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
String[] getPackagesForUid(int uid);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
@@ -381,11 +385,12 @@
String[] getSystemSharedLibraryNames();
/**
- * @return if the given package has a state and isn't filtered by visibility. Provides no
- * guarantee that the package is in any usable state.
+ * @return the state if the given package has a state and isn't filtered by visibility.
+ * Provides no guarantee that the package is in any usable state.
*/
@Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
- boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
+ @Nullable
+ PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
@UserIdInt int userId);
@Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
@@ -623,4 +628,16 @@
@Nullable
ArrayMap<String, ProcessInfo> getProcessesForUid(int uid);
// End block
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName);
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @NonNull
+ WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries();
+
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @Nullable
+ Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index cca1b97..691bf9f 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -133,9 +133,9 @@
import com.android.server.pm.pkg.PackageStateImpl;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.PackageUserStateUtils;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedIntentInfo;
@@ -298,11 +298,6 @@
public Collection<SharedUserSetting> getAllSharedUsers() {
return mSettings.getAllSharedUsersLPw();
}
-
- @Nullable
- public String getHarmfulAppWarning(@NonNull String packageName, @UserIdInt int userId) {
- return mSettings.getHarmfulAppWarningLPr(packageName, userId);
- }
}
private static final Comparator<ProviderInfo> sProviderInitOrderSorter = (p1, p2) -> {
@@ -829,7 +824,7 @@
}
public AndroidPackage getPackage(String packageName) {
- packageName = resolveInternalPackageNameLPr(
+ packageName = resolveInternalPackageName(
packageName, PackageManager.VERSION_CODE_HIGHEST);
return mPackages.get(packageName);
}
@@ -903,7 +898,7 @@
int filterCallingUid, int userId) {
// writer
// Normalize package name to handle renamed packages and static libs
- packageName = resolveInternalPackageNameLPr(packageName,
+ packageName = resolveInternalPackageName(packageName,
PackageManager.VERSION_CODE_HIGHEST);
AndroidPackage p = mPackages.get(packageName);
@@ -1575,7 +1570,7 @@
PackageInfo pi = new PackageInfo();
pi.packageName = ps.getPackageName();
pi.setLongVersionCode(ps.getVersionCode());
- pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().name : null;
+ pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().getName() : null;
pi.firstInstallTime = state.getFirstInstallTime();
pi.lastUpdateTime = ps.getLastUpdateTime();
@@ -1627,7 +1622,7 @@
long flags, int filterCallingUid, int userId) {
// reader
// Normalize package name to handle renamed packages and static libs
- packageName = resolveInternalPackageNameLPr(packageName, versionCode);
+ packageName = resolveInternalPackageName(packageName, versionCode);
final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
if (matchFactoryOnly) {
@@ -2111,7 +2106,7 @@
return packageName;
}
- public final String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+ public final String resolveInternalPackageName(String packageName, long versionCode) {
final int callingUid = Binder.getCallingUid();
return resolveInternalPackageNameInternalLocked(packageName, versionCode,
callingUid);
@@ -3489,7 +3484,9 @@
return mSettings.getRenamedPackageLPr(packageName);
}
- private WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
getSharedLibraries() {
return mSharedLibraries.getAll();
}
@@ -4070,10 +4067,13 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
@UserIdInt int userId) {
- final PackageStateInternal ps = getPackageStateInternal(packageName);
- return ps != null && !shouldFilterApplication(ps, callingUid, userId);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || shouldFilterApplication(packageState, callingUid, userId)) {
+ return null;
+ }
+ return packageState;
}
@Override
@@ -4576,9 +4576,9 @@
}
} else {
list = new ArrayList<>(mPackages.size());
- for (AndroidPackage p : mPackages.values()) {
- final PackageStateInternal packageState = packageStates.get(p.getPackageName());
- if (packageState == null) {
+ for (PackageStateInternal packageState : packageStates.values()) {
+ final AndroidPackage pkg = packageState.getPkg();
+ if (pkg == null) {
continue;
}
if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) {
@@ -4587,10 +4587,10 @@
if (shouldFilterApplication(packageState, callingUid, userId)) {
continue;
}
- ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(p, flags,
+ ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags,
packageState.getUserStateOrDefault(userId), userId, packageState);
if (ai != null) {
- ai.packageName = resolveExternalPackageName(p);
+ ai.packageName = resolveExternalPackageName(pkg);
list.add(ai);
}
}
@@ -5421,13 +5421,14 @@
return EmptyArray.STRING;
}
- ArraySet<PackageSetting> packages = packageSetting.getSharedUser().packages;
+ ArraySet<? extends PackageStateInternal> packages =
+ packageSetting.getSharedUser().getPackageStates();
final int numPackages = packages.size();
String[] res = new String[numPackages];
int i = 0;
for (int index = 0; index < numPackages; index++) {
- final PackageSetting ps = packages.valueAt(index);
- if (ps.getInstalled(userId)) {
+ final PackageStateInternal ps = packages.valueAt(index);
+ if (ps.getUserStateOrDefault(userId).isInstalled()) {
res[i++] = ps.getPackageName();
}
}
@@ -5476,7 +5477,11 @@
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
- return mSettings.getHarmfulAppWarning(packageName, userId);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ return packageState.getUserStateOrDefault(userId).getHarmfulAppWarning();
}
/**
@@ -5571,4 +5576,22 @@
}
return null;
}
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ return mSettings.getBlockUninstall(userId, packageName);
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ final SettingBase settingBase = mSettings.getSettingBase(appId);
+ if (settingBase instanceof SharedUserSetting) {
+ return Pair.create(null, (SharedUserApi) settingBase);
+ } else if (settingBase instanceof PackageSetting) {
+ return Pair.create((PackageStateInternal) settingBase, null);
+ } else {
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java
index 529aca3..40d4c03 100644
--- a/services/core/java/com/android/server/pm/ComputerLocked.java
+++ b/services/core/java/com/android/server/pm/ComputerLocked.java
@@ -40,12 +40,16 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -390,14 +394,6 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
- @UserIdInt int userId) {
- synchronized (mLock) {
- return super.isPackageStateAvailableAndVisible(packageName, callingUid, userId);
- }
- }
-
- @Override
public int checkSignatures(@NonNull String pkg1,
@NonNull String pkg2) {
synchronized (mLock) {
@@ -825,4 +821,35 @@
return super.getProcessesForUid(uid);
}
}
+
+ @Override
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.getPackageStateFiltered(packageName, callingUid, userId);
+ }
+ }
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mLock) {
+ return super.getBlockUninstall(userId, packageName);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ synchronized (mLock) {
+ return super.getSharedLibraries();
+ }
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ synchronized (mLock) {
+ return super.getPackageOrSharedUser(appId);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java
index 52309ce..24c08d1 100644
--- a/services/core/java/com/android/server/pm/ComputerTracker.java
+++ b/services/core/java/com/android/server/pm/ComputerTracker.java
@@ -42,11 +42,15 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -63,26 +67,11 @@
// a snapshot computer.
private final AtomicInteger mReusedSnapshot = new AtomicInteger(0);
- // The number of times a thread reused a computer in its stack instead of fetching
- // a live computer.
- private final AtomicInteger mReusedLive = new AtomicInteger(0);
-
private final PackageManagerService mService;
ComputerTracker(PackageManagerService s) {
mService = s;
}
- private ThreadComputer live() {
- ThreadComputer current = PackageManagerService.sThreadComputer.get();
- if (current.mRefCount > 0) {
- current.acquire();
- mReusedLive.incrementAndGet();
- } else {
- current.acquire(mService.liveComputer());
- }
- return current;
- }
-
private ThreadComputer snapshot() {
ThreadComputer current = PackageManagerService.sThreadComputer.get();
if (current.mRefCount > 0) {
@@ -133,7 +122,7 @@
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits,
String pkgName, String instantAppPkgName) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.queryIntentActivitiesInternalBody(intent, resolvedType,
flags, filterCallingUid, userId, resolveForStart, allowDynamicSplits,
@@ -154,7 +143,7 @@
public ActivityInfo getActivityInfoInternal(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags,
int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getActivityInfoInternal(component, flags, filterCallingUid,
userId);
@@ -180,7 +169,7 @@
}
public ApplicationInfo generateApplicationInfoFromSettings(String packageName,
long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.generateApplicationInfoFromSettings(packageName, flags,
filterCallingUid, userId);
@@ -199,7 +188,7 @@
}
public ApplicationInfo getApplicationInfoInternal(String packageName,
@PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getApplicationInfoInternal(packageName, flags,
filterCallingUid, userId);
@@ -208,7 +197,7 @@
}
}
public ComponentName getDefaultHomeActivity(int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getDefaultHomeActivity(userId);
} finally {
@@ -217,7 +206,7 @@
}
public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getHomeActivitiesAsUser(allHomeCandidates, userId);
} finally {
@@ -227,7 +216,7 @@
public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
int parentUserId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getCrossProfileDomainPreferredLpr(intent, resolvedType,
flags, sourceUserId, parentUserId);
@@ -236,7 +225,7 @@
}
}
public Intent getHomeIntent() {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getHomeIntent();
} finally {
@@ -245,7 +234,7 @@
}
public List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(
Intent intent, String resolvedType, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
userId);
@@ -257,7 +246,7 @@
@NonNull List<ResolveInfo> resolveInfos,
String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
boolean resolveForStart, int userId, Intent intent) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.applyPostResolutionFilter(resolveInfos, ephemeralPkgName,
allowDynamicSplits, filterCallingUid, resolveForStart, userId, intent);
@@ -267,7 +256,7 @@
}
public PackageInfo generatePackageInfo(PackageStateInternal ps,
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.generatePackageInfo(ps, flags, userId);
} finally {
@@ -285,7 +274,7 @@
}
public PackageInfo getPackageInfoInternal(String packageName, long versionCode,
long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageInfoInternal(packageName, versionCode, flags,
filterCallingUid, userId);
@@ -302,7 +291,7 @@
}
}
public PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageStateInternal(packageName, callingUid);
} finally {
@@ -312,7 +301,7 @@
@Nullable
public PackageState getPackageStateCopied(@NonNull String packageName) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageStateCopied(packageName);
} finally {
@@ -330,7 +319,7 @@
}
public ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
int sourceUserId, int targetUserId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.createForwardingResolveInfoUnchecked(filter, sourceUserId,
targetUserId);
@@ -340,7 +329,7 @@
}
public ServiceInfo getServiceInfo(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getServiceInfo(component, flags, userId);
} finally {
@@ -380,17 +369,17 @@
}
}
public String resolveExternalPackageName(AndroidPackage pkg) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.resolveExternalPackageName(pkg);
} finally {
current.release();
}
}
- public String resolveInternalPackageNameLPr(String packageName, long versionCode) {
- ThreadComputer current = live();
+ public String resolveInternalPackageName(String packageName, long versionCode) {
+ ThreadComputer current = snapshot();
try {
- return current.mComputer.resolveInternalPackageNameLPr(packageName, versionCode);
+ return current.mComputer.resolveInternalPackageName(packageName, versionCode);
} finally {
current.release();
}
@@ -404,7 +393,7 @@
}
}
public UserInfo getProfileParent(int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getProfileParent(userId);
} finally {
@@ -412,7 +401,7 @@
}
}
public boolean canViewInstantApps(int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.canViewInstantApps(callingUid, userId);
} finally {
@@ -445,7 +434,7 @@
}
public boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.filterSharedLibPackage(ps, uid, userId, flags);
} finally {
@@ -453,7 +442,7 @@
}
}
public boolean isCallerSameApp(String packageName, int uid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isCallerSameApp(packageName, uid);
} finally {
@@ -461,7 +450,7 @@
}
}
public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isComponentVisibleToInstantApp(component);
} finally {
@@ -470,7 +459,7 @@
}
public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component,
@PackageManager.ComponentType int type) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isComponentVisibleToInstantApp(component, type);
} finally {
@@ -479,7 +468,7 @@
}
public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent,
int userId, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent,
userId, resolvedType, flags);
@@ -497,7 +486,7 @@
}
public boolean isInstantAppInternal(String packageName, @UserIdInt int userId,
int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isInstantAppInternal(packageName, userId, callingUid);
} finally {
@@ -506,7 +495,7 @@
}
public boolean isSameProfileGroup(@UserIdInt int callerUserId,
@UserIdInt int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isSameProfileGroup(callerUserId, userId);
} finally {
@@ -515,7 +504,7 @@
}
public boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(sus, callingUid, userId);
} finally {
@@ -525,7 +514,7 @@
public boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
@PackageManager.ComponentType int componentType, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(ps, callingUid, component,
componentType, userId);
@@ -535,7 +524,7 @@
}
public boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(ps, callingUid, userId);
} finally {
@@ -552,7 +541,7 @@
}
public int getPackageUidInternal(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageUidInternal(packageName, flags, userId,
callingUid);
@@ -561,7 +550,7 @@
}
}
public long updateFlagsForApplication(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForApplication(flags, userId);
} finally {
@@ -569,7 +558,7 @@
}
}
public long updateFlagsForComponent(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForComponent(flags, userId);
} finally {
@@ -577,7 +566,7 @@
}
}
public long updateFlagsForPackage(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForPackage(flags, userId);
} finally {
@@ -597,7 +586,7 @@
public long updateFlagsForResolve(long flags, int userId, int callingUid,
boolean wantInstantApps, boolean onlyExposedExplicitly,
boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForResolve(flags, userId, callingUid,
wantInstantApps, onlyExposedExplicitly,
@@ -607,7 +596,7 @@
}
}
public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.dump(type, fd, pw, dumpState);
} finally {
@@ -616,7 +605,7 @@
}
public void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserOrProfilePermission(callingUid, userId,
requireFullPermission, checkShell, message);
@@ -626,7 +615,7 @@
}
public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, message);
@@ -637,7 +626,7 @@
public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell,
boolean requirePermissionWhenSameUser, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
@@ -649,7 +638,7 @@
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug,
int userId, boolean queryMayBeFiltered) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
query, always, removeMatches, debug, userId, queryMayBeFiltered);
@@ -660,7 +649,7 @@
public ResolveInfo findPersistentPreferredActivityLP(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
List<ResolveInfo> query, boolean debug, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
flags, query, debug, userId);
@@ -834,15 +823,6 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
- @UserIdInt int userId) {
- try (ThreadComputer current = snapshot()) {
- return current.mComputer.isPackageStateAvailableAndVisible(packageName, callingUid,
- userId);
- }
- }
-
- @Override
public int checkSignatures(@NonNull String pkg1,
@NonNull String pkg2) {
try (ThreadComputer current = snapshot()) {
@@ -1272,4 +1252,35 @@
return current.mComputer.getProcessesForUid(uid);
}
}
+
+ @Override
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+ @UserIdInt int userId) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getPackageStateFiltered(packageName, callingUid, userId);
+ }
+ }
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getBlockUninstall(userId, packageName);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getSharedLibraries();
+ }
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getPackageOrSharedUser(appId);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 48689a8..bd36b47 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -62,6 +62,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -220,12 +221,12 @@
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
deleteFlags | PackageManager.DELETE_CHATTY, info, true);
}
+ if (res && pkg != null) {
+ mPm.mInstantAppRegistry.onPackageUninstalled(pkg, uninstalledPs,
+ info.mRemovedUsers);
+ }
synchronized (mPm.mLock) {
if (res) {
- if (pkg != null) {
- mPm.mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs,
- info.mRemovedUsers);
- }
mPm.updateSequenceNumberLP(uninstalledPs, info.mRemovedUsers);
mPm.updateInstantAppInstallerLocked(packageName);
}
@@ -421,9 +422,7 @@
}
if (clearPackageStateAndReturn) {
clearPackageStateForUserLIF(ps, userId, outInfo, flags);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(user);
- }
+ mPm.scheduleWritePackageRestrictions(user);
return;
}
}
@@ -622,7 +621,6 @@
final String packageName = versionedPackage.getPackageName();
final long versionCode = versionedPackage.getLongVersionCode();
- final String internalPackageName;
try {
if (mPm.mInjector.getLocalService(ActivityTaskManagerInternal.class)
@@ -636,10 +634,8 @@
e.rethrowFromSystemServer();
}
- synchronized (mPm.mLock) {
- // Normalize package name to handle renamed packages and static libs
- internalPackageName = mPm.resolveInternalPackageNameLPr(packageName, versionCode);
- }
+ // Normalize package name to handle renamed packages and static libs
+ final String internalPackageName = mPm.resolveInternalPackageName(packageName, versionCode);
final int uid = Binder.getCallingUid();
if (!isOrphaned(internalPackageName)
@@ -748,13 +744,8 @@
}
private boolean isOrphaned(String packageName) {
- // reader
- synchronized (mPm.mLock) {
- if (!mPm.mPackages.containsKey(packageName)) {
- return false;
- }
- return mPm.mSettings.isOrphaned(packageName);
- }
+ final PackageStateInternal packageState = mPm.getPackageStateInternal(packageName);
+ return packageState != null && packageState.getInstallSource().isOrphaned;
}
private boolean isCallerAllowedToSilentlyUninstall(int callingUid, String pkgName) {
@@ -894,7 +885,7 @@
int installedForUsersCount = 0;
synchronized (mPm.mLock) {
// Normalize package name to handle renamed packages and static libs
- final String internalPkgName = mPm.resolveInternalPackageNameLPr(packageName,
+ final String internalPkgName = mPm.resolveInternalPackageName(packageName,
versionCode);
final PackageSetting ps = mPm.mSettings.getPackageLPr(internalPkgName);
if (ps != null) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index dcad3ec..53eb9cf 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -71,10 +71,15 @@
private final PackageManagerService mPm;
public boolean isDexOptDialogShown() {
- return mDexOptDialogShown;
+ synchronized (mLock) {
+ return mDexOptDialogShown;
+ }
}
- @GuardedBy("mPm.mLock")
+ // TODO: Is this lock really necessary?
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private boolean mDexOptDialogShown;
DexOptHelper(PackageManagerService pm) {
@@ -191,7 +196,7 @@
numberOfPackagesVisited, numberOfPackagesToDexopt), true);
} catch (RemoteException e) {
}
- synchronized (mPm.mLock) {
+ synchronized (mLock) {
mDexOptDialogShown = true;
}
}
@@ -287,13 +292,12 @@
public ArraySet<String> getOptimizablePackages() {
ArraySet<String> pkgs = new ArraySet<>();
- synchronized (mPm.mLock) {
- for (AndroidPackage p : mPm.mPackages.values()) {
- if (mPm.mPackageDexOptimizer.canOptimizePackage(p)) {
- pkgs.add(p.getPackageName());
- }
+ mPm.forEachPackageState(packageState -> {
+ final AndroidPackage pkg = packageState.getPkg();
+ if (pkg != null && mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
+ pkgs.add(packageState.getPackageName());
}
- }
+ });
return pkgs;
}
@@ -415,14 +419,10 @@
public void forceDexOpt(String packageName) {
PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
- AndroidPackage pkg;
- PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkg = mPm.mPackages.get(packageName);
- pkgSetting = mPm.mSettings.getPackageLPr(packageName);
- if (pkg == null || pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
+ final PackageStateInternal packageState = mPm.getPackageStateInternal(packageName);
+ final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
+ if (packageState == null || pkg == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
synchronized (mPm.mInstallLock) {
@@ -430,7 +430,7 @@
// Whoever is calling forceDexOpt wants a compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, pkgSetting,
+ final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
new DexoptOptions(packageName,
getDefaultCompilerFilter(),
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index 55d1293..5ab0c4c 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -39,6 +39,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.function.BiConsumer;
/**
* Dumps PackageManagerService internal states.
@@ -117,7 +118,7 @@
}
// Normalize package name to handle renamed packages and static libs
- pkg = mPm.resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
+ pkg = mPm.resolveInternalPackageName(pkg, PackageManager.VERSION_CODE_HIGHEST);
pw.println(mPm.checkPermission(perm, pkg, user));
return;
@@ -369,33 +370,20 @@
}
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
+ mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
+ mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
+ mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
+ mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
mPm.dumpComputer(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
}
@@ -405,23 +393,16 @@
mPm.dumpComputer(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
mPm.dumpComputer(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
- synchronized (mPm.mLock) {
- mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+ mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+ mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
}
if (!checkin
@@ -461,28 +442,28 @@
pw.println();
}
pw.println("Package Changes:");
- synchronized (mPm.mLock) {
- pw.print(" Sequence number="); pw.println(mPm.mChangedPackagesSequenceNumber);
- final int numChangedPackages = mPm.mChangedPackages.size();
+ mPm.mChangedPackagesTracker.iterateAll((sequenceNumber, values) -> {
+ pw.print(" Sequence number="); pw.println(sequenceNumber);
+ final int numChangedPackages = values.size();
for (int i = 0; i < numChangedPackages; i++) {
- final SparseArray<String> changes = mPm.mChangedPackages.valueAt(i);
- pw.print(" User "); pw.print(mPm.mChangedPackages.keyAt(i)); pw.println(":");
+ final SparseArray<String> changes = values.valueAt(i);
+ pw.print(" User "); pw.print(values.keyAt(i)); pw.println(":");
final int numChanges = changes.size();
if (numChanges == 0) {
pw.print(" "); pw.println("No packages changed");
} else {
for (int j = 0; j < numChanges; j++) {
final String pkgName = changes.valueAt(j);
- final int sequenceNumber = changes.keyAt(j);
+ final int userSequenceNumber = changes.keyAt(j);
pw.print(" ");
pw.print("seq=");
- pw.print(sequenceNumber);
+ pw.print(userSequenceNumber);
pw.print(", package=");
pw.println(pkgName);
}
}
}
- }
+ });
}
if (!checkin
@@ -537,9 +518,7 @@
if (!checkin
&& dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
&& packageName == null) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
- }
+ mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
}
if (!checkin
@@ -558,9 +537,7 @@
if (dumpState.onTitlePrinted()) {
pw.println();
}
- synchronized (mPm.mLock) {
- mPm.mSettings.dumpReadMessagesLPr(pw, dumpState);
- }
+ mPm.mSettings.dumpReadMessages(pw, dumpState);
pw.println();
pw.println("Package warning messages:");
dumpCriticalInfo(pw, null);
diff --git a/services/core/java/com/android/server/pm/IncrementalProgressListener.java b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
index 018501d..fa11924 100644
--- a/services/core/java/com/android/server/pm/IncrementalProgressListener.java
+++ b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
@@ -18,6 +18,8 @@
import android.content.pm.IPackageLoadingProgressCallback;
+import com.android.server.pm.pkg.PackageStateInternal;
+
/**
* Loading progress callback, used to listen for progress changes and update package setting
*/
@@ -31,25 +33,24 @@
@Override
public void onPackageLoadingProgressChanged(float progress) {
- final PackageSetting ps;
- synchronized (mPm.mLock) {
- ps = mPm.mSettings.getPackageLPr(mPackageName);
- if (ps == null) {
- return;
- }
+ PackageStateInternal packageState = mPm.getPackageStateInternal(mPackageName);
+ if (packageState == null) {
+ return;
+ }
- boolean wasLoading = ps.isLoading();
- // Due to asynchronous progress reporting, incomplete progress might be received
- // after the app is migrated off incremental. Ignore such progress updates.
- if (wasLoading) {
- ps.setLoadingProgress(progress);
- // Only report the state change when loading state changes from loading to not
- if (!ps.isLoading()) {
- // Unregister progress listener
- mPm.mIncrementalManager.unregisterLoadingProgressCallbacks(ps.getPathString());
- // Make sure the information is preserved
- mPm.scheduleWriteSettings();
- }
+ boolean wasLoading = packageState.isLoading();
+ // Due to asynchronous progress reporting, incomplete progress might be received
+ // after the app is migrated off incremental. Ignore such progress updates.
+ if (wasLoading) {
+ mPm.commitPackageStateMutation(null, mPackageName,
+ state -> state.setLoadingProgress(progress));
+ // Only report the state change when loading state changes from loading to not
+ if (Math.abs(1.0f - progress) < 0.00000001f) {
+ // Unregister progress listener
+ mPm.mIncrementalManager
+ .unregisterLoadingProgressCallbacks(packageState.getPathString());
+ // Make sure the information is preserved
+ mPm.scheduleWriteSettings();
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d98626f..336da2a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -141,7 +141,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -168,10 +168,7 @@
import dalvik.system.VMRuntime;
-import libcore.io.IoUtils;
-
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestException;
@@ -350,7 +347,7 @@
commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
(parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
- mPm.mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
+ mPm.mInstantAppRegistry.addInstantApp(userId, pkgSetting.getAppId());
}
if (!IncrementalManager.isIncrementalPath(pkgSetting.getPathString())) {
@@ -1788,9 +1785,7 @@
*/
private void setUpFsVerityIfPossible(AndroidPackage pkg) throws Installer.InstallerException,
PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
- final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
- final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
- if (!standardMode && !legacyMode) {
+ if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
return;
}
@@ -1801,36 +1796,24 @@
// Collect files we care for fs-verity setup.
ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
- if (legacyMode) {
- synchronized (mPm.mLock) {
- final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
- if (ps != null && ps.isPrivileged()) {
- fsverityCandidates.put(pkg.getBaseApkPath(), null);
- for (String splitPath : pkg.getSplitCodePaths()) {
- fsverityCandidates.put(splitPath, null);
- }
- }
- }
- } else {
- // NB: These files will become only accessible if the signing key is loaded in kernel's
- // .fs-verity keyring.
- fsverityCandidates.put(pkg.getBaseApkPath(),
- VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
+ // NB: These files will become only accessible if the signing key is loaded in kernel's
+ // .fs-verity keyring.
+ fsverityCandidates.put(pkg.getBaseApkPath(),
+ VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
- final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
- pkg.getBaseApkPath());
- if (new File(dmPath).exists()) {
- fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
- }
+ final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
+ pkg.getBaseApkPath());
+ if (new File(dmPath).exists()) {
+ fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
+ }
- for (String path : pkg.getSplitCodePaths()) {
- fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
+ for (String path : pkg.getSplitCodePaths()) {
+ fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
- final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
- if (new File(splitDmPath).exists()) {
- fsverityCandidates.put(splitDmPath,
- VerityUtils.getFsveritySignatureFilePath(splitDmPath));
- }
+ final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
+ if (new File(splitDmPath).exists()) {
+ fsverityCandidates.put(splitDmPath,
+ VerityUtils.getFsveritySignatureFilePath(splitDmPath));
}
}
@@ -1839,43 +1822,14 @@
final String filePath = entry.getKey();
final String signaturePath = entry.getValue();
- if (!legacyMode) {
- // fs-verity is optional for now. Only set up if signature is provided.
- if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
- try {
- VerityUtils.setUpFsverity(filePath, signaturePath);
- } catch (IOException e) {
- throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
- "Failed to enable fs-verity: " + e);
- }
- }
- continue;
- }
-
- // In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
- final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
- if (result.isOk()) {
- if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
- final FileDescriptor fd = result.getUnownedFileDescriptor();
+ // fs-verity is optional for now. Only set up if signature is provided.
+ if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
try {
- final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
- try {
- // A file may already have fs-verity, e.g. when reused during a split
- // install. If the measurement succeeds, no need to attempt to set up.
- mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
- rootHash);
- } catch (Installer.InstallerException e) {
- mPm.mInstaller.installApkVerity(packageName, filePath, fd,
- result.getContentSize());
- mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
- rootHash);
- }
- } finally {
- IoUtils.closeQuietly(fd);
+ VerityUtils.setUpFsverity(filePath, signaturePath);
+ } catch (IOException e) {
+ throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
+ "Failed to enable fs-verity: " + e);
}
- } else if (result.isFailed()) {
- throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
- "Failed to generate verity");
}
}
}
@@ -2404,26 +2358,26 @@
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for updated system application.
if (installedPkg.isSystem()) {
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else {
// If current upgrade specifies particular preference
if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// Application explicitly specified internal.
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
} else if (
installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// App explicitly prefers external. Let policy decide
} else {
// Prefer previous location
if (installedPkg.isExternalStorage()) {
- return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
}
- return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+ return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
}
}
} else {
// Invalid install. Return error code
- return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+ return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
}
}
}
@@ -2660,9 +2614,7 @@
? res.mRemovedInfo.mInstallerPackageName
: null;
- synchronized (mPm.mLock) {
- mPm.mInstantAppRegistry.onPackageInstalledLPw(res.mPkg, res.mNewUsers);
- }
+ mPm.notifyInstantAppPackageInstalled(res.mPkg.getPackageName(), res.mNewUsers);
// Determine the set of users who are adding this package for
// the first time vs. those who are seeing an update.
@@ -3959,14 +3911,14 @@
*/
private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
final String packageName = pkg.getPackageName();
- if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
+ if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
return false;
}
// TODO: Allow base and splits to be verified individually.
String[] splitCodePaths = pkg.getSplitCodePaths();
if (!ArrayUtils.isEmpty(splitCodePaths)) {
for (int i = 0; i < splitCodePaths.length; i++) {
- if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
+ if (!VerityUtils.hasFsverity(splitCodePaths[i])) {
return false;
}
}
@@ -3975,34 +3927,6 @@
}
/**
- * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
- * whether the apk contains signed root hash. Note that the signer's certificate still needs to
- * match one in a trusted source, and should be done separately.
- */
- private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
- if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
- return VerityUtils.hasFsverity(apkPath);
- }
-
- try {
- final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
- if (rootHashObserved == null) {
- return false; // APK does not contain Merkle tree root hash.
- }
- synchronized (mPm.mInstallLock) {
- // Returns whether the observed root hash matches what kernel has.
- mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
- rootHashObserved);
- return true;
- }
- } catch (Installer.InstallerException | IOException | DigestException
- | NoSuchAlgorithmException e) {
- Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
- }
- return false;
- }
-
- /**
* Clear the package profile if this was an upgrade and the package
* version was updated.
*/
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index d996fe4..7e845c7 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -35,18 +35,17 @@
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.PackageLite;
-import android.os.Environment;
import android.os.Message;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.File;
import java.util.ArrayList;
@@ -142,12 +141,8 @@
* Only {@link PackageManager#INSTALL_INTERNAL} flag may mutate in
* {@link #mInstallFlags}
*/
- private int overrideInstallLocation(PackageInfoLite pkgLite) {
- final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
- if (DEBUG_INSTANT && ephemeral) {
- Slog.v(TAG, "pkgLite for install: " + pkgLite);
- }
-
+ private int overrideInstallLocation(String packageName, int recommendedInstallLocation,
+ int installLocation) {
if (mOriginInfo.mStaged) {
// If we're already staged, we've firmly committed to an install location
if (mOriginInfo.mFile != null) {
@@ -155,77 +150,35 @@
} else {
throw new IllegalStateException("Invalid stage location");
}
- } else if (pkgLite.recommendedInstallLocation
- == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
- /*
- * If we are not staged and have too little free space, try to free cache
- * before giving up.
- */
- // TODO: focus freeing disk space on the target device
- final StorageManager storage = StorageManager.from(mPm.mContext);
- final long lowThreshold = storage.getStorageLowBytes(
- Environment.getDataDirectory());
-
- final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
- mOriginInfo.mResolvedPath, mPackageAbiOverride);
- if (sizeBytes >= 0) {
- synchronized (mPm.mInstallLock) {
- try {
- mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
- pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext,
- mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags,
- mPackageAbiOverride);
- } catch (Installer.InstallerException e) {
- Slog.w(TAG, "Failed to free cache", e);
- }
- }
- }
-
- /*
- * The cache free must have deleted the file we downloaded to install.
- *
- * TODO: fix the "freeCache" call to not delete the file we care about.
- */
- if (pkgLite.recommendedInstallLocation
- == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
- pkgLite.recommendedInstallLocation =
- PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ }
+ if (recommendedInstallLocation < 0) {
+ return InstallLocationUtils.getInstallationErrorCode(recommendedInstallLocation);
+ }
+ // Override with defaults if needed.
+ synchronized (mPm.mLock) {
+ // reader
+ AndroidPackage installedPkg = mPm.mPackages.get(packageName);
+ if (installedPkg != null) {
+ // Currently installed package which the new package is attempting to replace
+ recommendedInstallLocation = InstallLocationUtils.installLocationPolicy(
+ installLocation, recommendedInstallLocation, mInstallFlags,
+ installedPkg.isSystem(), installedPkg.isExternalStorage());
}
}
- int ret = INSTALL_SUCCEEDED;
- int loc = pkgLite.recommendedInstallLocation;
- if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
- ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
- ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
- ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
- ret = PackageManager.INSTALL_FAILED_INVALID_APK;
- } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
- ret = PackageManager.INSTALL_FAILED_INVALID_URI;
- } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
- ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
- } else {
- // Override with defaults if needed.
- loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags);
+ final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
- final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
-
- if (!onInt) {
- // Override install location with flags
- if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
- // Set the flag to install on external media.
- mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
- } else {
- // Make sure the flag for installing on external
- // media is unset
- mInstallFlags |= PackageManager.INSTALL_INTERNAL;
- }
+ if (!onInt) {
+ // Override install location with flags
+ if (recommendedInstallLocation == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) {
+ // Set the flag to install on external media.
+ mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
+ } else {
+ // Make sure the flag for installing on external media is unset
+ mInstallFlags |= PackageManager.INSTALL_INTERNAL;
}
}
- return ret;
+ return INSTALL_SUCCEEDED;
}
/*
@@ -254,7 +207,21 @@
}
}
- mRet = overrideInstallLocation(pkgLite);
+ final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ if (DEBUG_INSTANT && ephemeral) {
+ Slog.v(TAG, "pkgLite for install: " + pkgLite);
+ }
+
+ if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
+ == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+ // If we are not staged and have too little free space, try to free cache
+ // before giving up.
+ pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
+ pkgLite.recommendedInstallLocation, mPackageLite,
+ mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
+ }
+ mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation,
+ pkgLite.installLocation);
}
@Override
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 47be7e6..c4389a7 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -39,7 +39,6 @@
import dalvik.system.BlockGuard;
import dalvik.system.VMRuntime;
-import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -726,34 +725,6 @@
}
}
- /**
- * Enables legacy apk-verity for an apk.
- */
- public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput,
- int contentSize) throws InstallerException {
- if (!checkBeforeRemote()) return;
- BlockGuard.getVmPolicy().onPathAccess(filePath);
- try {
- mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
- * Checks if provided hash matches the file's fs-verity merkle tree root hash.
- */
- public void assertFsverityRootHashMatches(String packageName, String filePath,
- @NonNull byte[] expectedHash) throws InstallerException {
- if (!checkBeforeRemote()) return;
- BlockGuard.getVmPolicy().onPathAccess(filePath);
- try {
- mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
for (int i = 0; i < isas.length; i++) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 1de239e..ea6e458 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -18,12 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.SigningDetails;
import android.graphics.Bitmap;
@@ -93,7 +94,7 @@
* pruning installed instant apps and meta-data for uninstalled instant apps
* when free space is needed.
*/
-class InstantAppRegistry implements Watchable, Snappable {
+public class InstantAppRegistry implements Watchable, Snappable {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "InstantAppRegistry";
@@ -125,14 +126,17 @@
private static final String ATTR_NAME = "name";
private static final String ATTR_GRANTED = "granted";
- private final PackageManagerService mService;
+ private final Context mContext;
private final PermissionManagerServiceInternal mPermissionManager;
+ private final UserManagerInternal mUserManager;
+ private final DeletePackageHelper mDeletePackageHelper;
private final CookiePersistence mCookiePersistence;
- private final PackageManagerInternal mPmInternal;
+
+ private final Object mLock = new Object();
/** State for uninstalled instant apps */
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
/**
@@ -142,12 +146,12 @@
* UserID -> TargetAppId -> InstantAppId
*/
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<WatchedSparseArray<WatchedSparseBooleanArray>> mInstantGrants;
/** The set of all installed instant apps. UserID -> AppID */
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<WatchedSparseBooleanArray> mInstalledInstantAppUids;
/**
@@ -159,6 +163,7 @@
* Watchable machinery
*/
private final WatchableImpl mWatchable = new WatchableImpl();
+
public void registerObserver(@NonNull Watcher observer) {
mWatchable.registerObserver(observer);
}
@@ -196,12 +201,14 @@
}};
}
- public InstantAppRegistry(PackageManagerService service,
- PermissionManagerServiceInternal permissionManager,
- PackageManagerInternal pmInternal) {
- mService = service;
+ public InstantAppRegistry(@NonNull Context context,
+ @NonNull PermissionManagerServiceInternal permissionManager,
+ @NonNull UserManagerInternal userManager,
+ @NonNull DeletePackageHelper deletePackageHelper) {
+ mContext = context;
mPermissionManager = permissionManager;
- mPmInternal = pmInternal;
+ mUserManager = userManager;
+ mDeletePackageHelper = deletePackageHelper;
mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
mUninstalledInstantApps = new WatchedSparseArray<List<UninstalledInstantAppState>>();
@@ -220,9 +227,10 @@
* The copy constructor is used by PackageManagerService to construct a snapshot.
*/
private InstantAppRegistry(InstantAppRegistry r) {
- mService = r.mService;
+ mContext = r.mContext;
mPermissionManager = r.mPermissionManager;
- mPmInternal = r.mPmInternal;
+ mUserManager = r.mUserManager;
+ mDeletePackageHelper = r.mDeletePackageHelper;
mCookiePersistence = null;
mUninstalledInstantApps = new WatchedSparseArray<List<UninstalledInstantAppState>>(
@@ -243,56 +251,44 @@
return mSnapshot.snapshot();
}
- @GuardedBy("mService.mLock")
- public byte[] getInstantAppCookieLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- // Only installed packages can get their own cookie
- AndroidPackage pkg = mService.mPackages.get(packageName);
- if (pkg == null) {
+ public byte[] getInstantAppCookie(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
+ synchronized (mLock) {
+ byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
+ if (pendingCookie != null) {
+ return pendingCookie;
+ }
+ File cookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
+ if (cookieFile != null && cookieFile.exists()) {
+ try {
+ return IoUtils.readFileAsByteArray(cookieFile.toString());
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
+ }
+ }
return null;
}
-
- byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
- if (pendingCookie != null) {
- return pendingCookie;
- }
- File cookieFile = peekInstantCookieFile(packageName, userId);
- if (cookieFile != null && cookieFile.exists()) {
- try {
- return IoUtils.readFileAsByteArray(cookieFile.toString());
- } catch (IOException e) {
- Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
- }
- }
- return null;
}
- @GuardedBy("mService.mLock")
- public boolean setInstantAppCookieLPw(@NonNull String packageName,
- @Nullable byte[] cookie, @UserIdInt int userId) {
- if (cookie != null && cookie.length > 0) {
- final int maxCookieSize = mService.mContext.getPackageManager()
- .getInstantAppCookieMaxBytes();
- if (cookie.length > maxCookieSize) {
- Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
- + cookie.length + " bytes while max size is " + maxCookieSize);
- return false;
+ public boolean setInstantAppCookie(@NonNull AndroidPackage pkg,
+ @Nullable byte[] cookie, int instantAppCookieMaxBytes, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (cookie != null && cookie.length > 0) {
+ if (cookie.length > instantAppCookieMaxBytes) {
+ Slog.e(LOG_TAG, "Instant app cookie for package " + pkg.getPackageName()
+ + " size " + cookie.length + " bytes while max size is "
+ + instantAppCookieMaxBytes);
+ return false;
+ }
}
- }
- // Only an installed package can set its own cookie
- AndroidPackage pkg = mService.mPackages.get(packageName);
- if (pkg == null) {
- return false;
+ mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
+ return true;
}
-
- mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
- return true;
}
private void persistInstantApplicationCookie(@Nullable byte[] cookie,
@NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
- synchronized (mService.mLock) {
+ synchronized (mLock) {
File appDir = getInstantApplicationDir(packageName, userId);
if (!appDir.exists() && !appDir.mkdirs()) {
Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
@@ -315,55 +311,54 @@
}
}
- public Bitmap getInstantAppIconLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- File iconFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ICON_FILE);
- if (iconFile.exists()) {
- return BitmapFactory.decodeFile(iconFile.toString());
- }
- return null;
- }
-
- public String getInstantAppAndroidIdLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- File idFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ANDROID_ID_FILE);
- if (idFile.exists()) {
- try {
- return IoUtils.readFileAsString(idFile.getAbsolutePath());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+ @Nullable
+ public Bitmap getInstantAppIcon(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ File iconFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ICON_FILE);
+ if (iconFile.exists()) {
+ return BitmapFactory.decodeFile(iconFile.toString());
}
+ return null;
}
- return generateInstantAppAndroidIdLPw(packageName, userId);
}
- private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- byte[] randomBytes = new byte[8];
- new SecureRandom().nextBytes(randomBytes);
- String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
- File appDir = getInstantApplicationDir(packageName, userId);
- if (!appDir.exists() && !appDir.mkdirs()) {
- Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
+ @Nullable
+ public String getInstantAppAndroidId(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ File idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ if (idFile.exists()) {
+ try {
+ return IoUtils.readFileAsString(idFile.getAbsolutePath());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+ }
+ }
+
+ byte[] randomBytes = new byte[8];
+ new SecureRandom().nextBytes(randomBytes);
+ String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
+ File appDir = getInstantApplicationDir(packageName, userId);
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
+ return id;
+ }
+ idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ try (FileOutputStream fos = new FileOutputStream(idFile)) {
+ fos.write(id.getBytes());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
+ }
return id;
}
- File idFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ANDROID_ID_FILE);
- try (FileOutputStream fos = new FileOutputStream(idFile)) {
- fos.write(id.getBytes());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
- }
- return id;
-
}
- @GuardedBy("mService.mLock")
- public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
- List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
- List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
+ @Nullable
+ public List<InstantAppInfo> getInstantApps(@NonNull Computer computer, @UserIdInt int userId) {
+ List<InstantAppInfo> installedApps = getInstalledInstantApplications(computer, userId);
+ List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplications(computer, userId);
if (installedApps != null) {
if (uninstalledApps != null) {
installedApps.addAll(uninstalledApps);
@@ -373,123 +368,130 @@
return uninstalledApps;
}
- @GuardedBy("mService.mLock")
- public void onPackageInstalledLPw(@NonNull AndroidPackage pkg, @NonNull int[] userIds) {
- PackageStateInternal ps = mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
+ public void onPackageInstalled(@NonNull Computer computer, @NonNull String packageName,
+ @NonNull int[] userIds) {
+ PackageStateInternal ps = computer.getPackageStateInternal(packageName);
+ AndroidPackage pkg = ps == null ? null : ps.getPkg();
+ if (pkg == null) {
return;
}
- for (int userId : userIds) {
- // Ignore not installed apps
- if (mService.mPackages.get(pkg.getPackageName()) == null
- || !ps.getUserStateOrDefault(userId).isInstalled()) {
- continue;
- }
+ synchronized (mLock) {
+ for (int userId : userIds) {
+ // Ignore not installed apps
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ continue;
+ }
- // Propagate permissions before removing any state
- propagateInstantAppPermissionsIfNeeded(pkg, userId);
+ // Propagate permissions before removing any state
+ propagateInstantAppPermissionsIfNeeded(pkg, userId);
- // Track instant apps
- if (ps.getUserStateOrDefault(userId).isInstantApp()) {
- addInstantAppLPw(userId, ps.getAppId());
- }
+ // Track instant apps
+ if (ps.getUserStateOrDefault(userId).isInstantApp()) {
+ addInstantApp(userId, ps.getAppId());
+ }
- // Remove the in-memory state
- removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
- state.mInstantAppInfo.getPackageName().equals(pkg.getPackageName()),
- userId);
+ // Remove the in-memory state
+ removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+ state.mInstantAppInfo.getPackageName().equals(pkg.getPackageName()),
+ userId);
- // Remove the on-disk state except the cookie
- File instantAppDir = getInstantApplicationDir(pkg.getPackageName(), userId);
- new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+ // Remove the on-disk state except the cookie
+ File instantAppDir = getInstantApplicationDir(pkg.getPackageName(), userId);
+ new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
- // If app signature changed - wipe the cookie
- File currentCookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
- if (currentCookieFile == null) {
- continue;
- }
+ // If app signature changed - wipe the cookie
+ File currentCookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
+ if (currentCookieFile == null) {
+ continue;
+ }
- String cookieName = currentCookieFile.getName();
- String currentCookieSha256 =
- cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(),
- cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length());
+ String cookieName = currentCookieFile.getName();
+ String currentCookieSha256 =
+ cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(),
+ cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length());
- // Before we used only the first signature to compute the SHA 256 but some
- // apps could be singed by multiple certs and the cert order is undefined.
- // We prefer the modern computation procedure where all certs are taken
- // into account but also allow the value from the old computation to avoid
- // data loss.
- if (pkg.getSigningDetails().checkCapability(currentCookieSha256,
- SigningDetails.CertCapabilities.INSTALLED_DATA)) {
- return;
- }
-
- // For backwards compatibility we accept match based on any signature, since we may have
- // recorded only the first for multiply-signed packages
- final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
- pkg.getSigningDetails().getSignatures());
- for (String s : signaturesSha256Digests) {
- if (s.equals(currentCookieSha256)) {
+ // Before we used only the first signature to compute the SHA 256 but some
+ // apps could be singed by multiple certs and the cert order is undefined.
+ // We prefer the modern computation procedure where all certs are taken
+ // into account but also allow the value from the old computation to avoid
+ // data loss.
+ if (pkg.getSigningDetails().checkCapability(currentCookieSha256,
+ SigningDetails.CertCapabilities.INSTALLED_DATA)) {
return;
}
- }
- // Sorry, you are out of luck - different signatures - nuke data
- Slog.i(LOG_TAG, "Signature for package " + pkg.getPackageName()
- + " changed - dropping cookie");
+ // For backwards compatibility we accept match based on any signature, since we may
+ // have recorded only the first for multiply-signed packages
+ final String[] signaturesSha256Digests =
+ PackageUtils.computeSignaturesSha256Digests(
+ pkg.getSigningDetails().getSignatures());
+ for (String s : signaturesSha256Digests) {
+ if (s.equals(currentCookieSha256)) {
+ return;
+ }
+ }
+
+ // Sorry, you are out of luck - different signatures - nuke data
+ Slog.i(LOG_TAG, "Signature for package " + pkg.getPackageName()
+ + " changed - dropping cookie");
// Make sure a pending write for the old signed app is cancelled
- mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
- currentCookieFile.delete();
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
+ currentCookieFile.delete();
+ }
}
}
- @GuardedBy("mService.mLock")
- public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps,
+ public void onPackageUninstalled(@NonNull AndroidPackage pkg, @NonNull PackageSetting ps,
@NonNull int[] userIds) {
if (ps == null) {
return;
}
- for (int userId : userIds) {
- if (mService.mPackages.get(pkg.getPackageName()) != null && ps.getInstalled(userId)) {
- continue;
- }
+ synchronized (mLock) {
+ for (int userId : userIds) {
+ if (ps.getInstalled(userId)) {
+ continue;
+ }
- if (ps.getInstantApp(userId)) {
- // Add a record for an uninstalled instant app
- addUninstalledInstantAppLPw(pkg, userId);
- removeInstantAppLPw(userId, ps.getAppId());
- } else {
- // Deleting an app prunes all instant state such as cookie
- deleteDir(getInstantApplicationDir(pkg.getPackageName(), userId));
- mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
- removeAppLPw(userId, ps.getAppId());
+ if (ps.getInstantApp(userId)) {
+ // Add a record for an uninstalled instant app
+ addUninstalledInstantAppLPw(ps, userId);
+ removeInstantAppLPw(userId, ps.getAppId());
+ } else {
+ // Deleting an app prunes all instant state such as cookie
+ deleteDir(getInstantApplicationDir(pkg.getPackageName(), userId));
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
+ removeAppLPw(userId, ps.getAppId());
+ }
}
}
}
- @GuardedBy("mService.mLock")
- public void onUserRemovedLPw(int userId) {
- mUninstalledInstantApps.remove(userId);
- mInstalledInstantAppUids.remove(userId);
- mInstantGrants.remove(userId);
- deleteDir(getInstantApplicationsDir(userId));
+ public void onUserRemoved(int userId) {
+ synchronized (mLock) {
+ mUninstalledInstantApps.remove(userId);
+ mInstalledInstantAppUids.remove(userId);
+ mInstantGrants.remove(userId);
+ deleteDir(getInstantApplicationsDir(userId));
+ }
}
public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
int instantAppId) {
- final WatchedSparseArray<WatchedSparseBooleanArray> targetAppList =
- mInstantGrants.get(userId);
- if (targetAppList == null) {
- return false;
+ synchronized (mLock) {
+ final WatchedSparseArray<WatchedSparseBooleanArray> targetAppList =
+ mInstantGrants.get(userId);
+ if (targetAppList == null) {
+ return false;
+ }
+ final WatchedSparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
+ if (instantGrantList == null) {
+ return false;
+ }
+ return instantGrantList.get(instantAppId);
}
- final WatchedSparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
- if (instantGrantList == null) {
- return false;
- }
- return instantGrantList.get(instantAppId);
}
/**
@@ -503,51 +505,54 @@
* to the recipient
* @return {@code true} if access is granted.
*/
- @GuardedBy("mService.mLock")
- public boolean grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
+ public boolean grantInstantAccess(@UserIdInt int userId, @Nullable Intent intent,
int recipientUid, int instantAppId) {
- if (mInstalledInstantAppUids == null) {
- return false; // no instant apps installed; no need to grant
- }
- WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
- if (instantAppList == null || !instantAppList.get(instantAppId)) {
- return false; // instant app id isn't installed; no need to grant
- }
- if (instantAppList.get(recipientUid)) {
- return false; // target app id is an instant app; no need to grant
- }
- if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
- final Set<String> categories = intent.getCategories();
- if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
- return false; // launched via VIEW/BROWSABLE intent; no need to grant
+ synchronized (mLock) {
+ if (mInstalledInstantAppUids == null) {
+ return false; // no instant apps installed; no need to grant
}
+ WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+ if (instantAppList == null || !instantAppList.get(instantAppId)) {
+ return false; // instant app id isn't installed; no need to grant
+ }
+ if (instantAppList.get(recipientUid)) {
+ return false; // target app id is an instant app; no need to grant
+ }
+ if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+ final Set<String> categories = intent.getCategories();
+ if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
+ return false; // launched via VIEW/BROWSABLE intent; no need to grant
+ }
+ }
+ WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(
+ userId);
+ if (targetAppList == null) {
+ targetAppList = new WatchedSparseArray<>();
+ mInstantGrants.put(userId, targetAppList);
+ }
+ WatchedSparseBooleanArray instantGrantList = targetAppList.get(recipientUid);
+ if (instantGrantList == null) {
+ instantGrantList = new WatchedSparseBooleanArray();
+ targetAppList.put(recipientUid, instantGrantList);
+ }
+ instantGrantList.put(instantAppId, true /*granted*/);
+ return true;
}
- WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(userId);
- if (targetAppList == null) {
- targetAppList = new WatchedSparseArray<>();
- mInstantGrants.put(userId, targetAppList);
- }
- WatchedSparseBooleanArray instantGrantList = targetAppList.get(recipientUid);
- if (instantGrantList == null) {
- instantGrantList = new WatchedSparseBooleanArray();
- targetAppList.put(recipientUid, instantGrantList);
- }
- instantGrantList.put(instantAppId, true /*granted*/);
- return true;
}
- @GuardedBy("mService.mLock")
- public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
- WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
- if (instantAppList == null) {
- instantAppList = new WatchedSparseBooleanArray();
- mInstalledInstantAppUids.put(userId, instantAppList);
+ public void addInstantApp(@UserIdInt int userId, int instantAppId) {
+ synchronized (mLock) {
+ WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+ if (instantAppList == null) {
+ instantAppList = new WatchedSparseBooleanArray();
+ mInstalledInstantAppUids.put(userId, instantAppList);
+ }
+ instantAppList.put(instantAppId, true /*installed*/);
}
- instantAppList.put(instantAppId, true /*installed*/);
onChanged();
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
// remove from the installed list
if (mInstalledInstantAppUids == null) {
@@ -578,7 +583,7 @@
}
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
// remove from the installed list
if (mInstantGrants == null) {
@@ -593,11 +598,11 @@
onChanged();
}
- @GuardedBy("mService.mLock")
- private void addUninstalledInstantAppLPw(@NonNull AndroidPackage pkg,
+ @GuardedBy("mLock")
+ private void addUninstalledInstantAppLPw(@NonNull PackageStateInternal packageState,
@UserIdInt int userId) {
InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
- pkg, userId, false);
+ packageState, userId, false);
if (uninstalledApp == null) {
return;
}
@@ -612,7 +617,7 @@
uninstalledAppStates.add(uninstalledAppState);
writeUninstalledInstantAppMetadata(uninstalledApp, userId);
- writeInstantApplicationIconLPw(pkg, userId);
+ writeInstantApplicationIconLPw(packageState.getPkg(), userId);
}
private void writeInstantApplicationIconLPw(@NonNull AndroidPackage pkg,
@@ -624,7 +629,7 @@
// TODO(b/135203078): Remove toAppInfo call? Requires significant additions/changes to PM
Drawable icon = AndroidPackageUtils.generateAppInfoWithoutState(pkg)
- .loadIcon(mService.mContext.getPackageManager());
+ .loadIcon(mContext.getPackageManager());
final Bitmap bitmap;
if (icon instanceof BitmapDrawable) {
@@ -647,30 +652,30 @@
}
}
- @GuardedBy("mService.mLock")
- boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
- return hasUninstalledInstantAppStateLPr(packageName, userId)
- || hasInstantAppMetadataLPr(packageName, userId);
+ boolean hasInstantApplicationMetadata(String packageName, int userId) {
+ return hasUninstalledInstantAppState(packageName, userId)
+ || hasInstantAppMetadata(packageName, userId);
}
- @GuardedBy("mService.mLock")
- public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
+ public void deleteInstantApplicationMetadata(@NonNull String packageName,
@UserIdInt int userId) {
- removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
- state.mInstantAppInfo.getPackageName().equals(packageName),
- userId);
+ synchronized (mLock) {
+ removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+ state.mInstantAppInfo.getPackageName().equals(packageName),
+ userId);
- File instantAppDir = getInstantApplicationDir(packageName, userId);
- new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
- File cookie = peekInstantCookieFile(packageName, userId);
- if (cookie != null) {
- cookie.delete();
+ File instantAppDir = getInstantApplicationDir(packageName, userId);
+ new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
+ File cookie = peekInstantCookieFile(packageName, userId);
+ if (cookie != null) {
+ cookie.delete();
+ }
}
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeUninstalledInstantAppStateLPw(
@NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
if (mUninstalledInstantApps == null) {
@@ -696,27 +701,28 @@
}
}
- @GuardedBy("mService.mLock")
- private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
- if (mUninstalledInstantApps == null) {
- return false;
- }
- final List<UninstalledInstantAppState> uninstalledAppStates =
- mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates == null) {
- return false;
- }
- final int appCount = uninstalledAppStates.size();
- for (int i = 0; i < appCount; i++) {
- final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
- if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
- return true;
+ private boolean hasUninstalledInstantAppState(String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (mUninstalledInstantApps == null) {
+ return false;
}
+ final List<UninstalledInstantAppState> uninstalledAppStates =
+ mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates == null) {
+ return false;
+ }
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+ if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
}
- return false;
}
- private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
+ private boolean hasInstantAppMetadata(String packageName, @UserIdInt int userId) {
final File instantAppDir = getInstantApplicationDir(packageName, userId);
return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
|| new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
@@ -724,37 +730,41 @@
|| peekInstantCookieFile(packageName, userId) != null;
}
- void pruneInstantApps() {
+ void pruneInstantApps(@NonNull Computer computer) {
final long maxInstalledCacheDuration = Settings.Global.getLong(
- mService.mContext.getContentResolver(),
+ mContext.getContentResolver(),
Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
final long maxUninstalledCacheDuration = Settings.Global.getLong(
- mService.mContext.getContentResolver(),
+ mContext.getContentResolver(),
Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
try {
- pruneInstantApps(Long.MAX_VALUE,
+ pruneInstantApps(computer, Long.MAX_VALUE,
maxInstalledCacheDuration, maxUninstalledCacheDuration);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
}
}
- boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
+ boolean pruneInstalledInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxInstalledCacheDuration) {
try {
- return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
+ return pruneInstantApps(computer, neededSpace, maxInstalledCacheDuration,
+ Long.MAX_VALUE);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
return false;
}
}
- boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
+ boolean pruneUninstalledInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxUninstalledCacheDuration) {
try {
- return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
+ return pruneInstantApps(computer, neededSpace, Long.MAX_VALUE,
+ maxUninstalledCacheDuration);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
return false;
@@ -774,9 +784,9 @@
*
* @throws IOException
*/
- private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
- long maxUninstalledCacheDuration) throws IOException {
- final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
+ private boolean pruneInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxInstalledCacheDuration, long maxUninstalledCacheDuration) throws IOException {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
if (file.getUsableSpace() >= neededSpace) {
@@ -789,105 +799,105 @@
final long now = System.currentTimeMillis();
// Prune first installed instant apps
- synchronized (mService.mLock) {
- allUsers = mService.mUserManager.getUserIds();
+ allUsers = mUserManager.getUserIds();
- final int packageCount = mService.mPackages.size();
- for (int i = 0; i < packageCount; i++) {
- final AndroidPackage pkg = mService.mPackages.valueAt(i);
- final PackageStateInternal ps =
- mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
- continue;
- }
-
- if (now - ps.getTransientState().getLatestPackageUseTimeInMills()
- < maxInstalledCacheDuration) {
- continue;
- }
-
- boolean installedOnlyAsInstantApp = false;
- for (int userId : allUsers) {
- final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
- if (userState.isInstalled()) {
- if (userState.isInstantApp()) {
- installedOnlyAsInstantApp = true;
- } else {
- installedOnlyAsInstantApp = false;
- break;
- }
- }
- }
- if (installedOnlyAsInstantApp) {
- if (packagesToDelete == null) {
- packagesToDelete = new ArrayList<>();
- }
- packagesToDelete.add(pkg.getPackageName());
- }
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ computer.getPackageStates();
+ final int packageStateCount = packageStates.size();
+ for (int i = 0; i < packageStateCount; i++) {
+ final PackageStateInternal ps = packageStates.valueAt(i);
+ final AndroidPackage pkg = ps == null ? null : ps.getPkg();
+ if (pkg == null) {
+ continue;
}
- if (packagesToDelete != null) {
- packagesToDelete.sort((String lhs, String rhs) -> {
- final AndroidPackage lhsPkg = mService.mPackages.get(lhs);
- final AndroidPackage rhsPkg = mService.mPackages.get(rhs);
- if (lhsPkg == null && rhsPkg == null) {
- return 0;
- } else if (lhsPkg == null) {
- return -1;
- } else if (rhsPkg == null) {
- return 1;
+ if (now - ps.getTransientState().getLatestPackageUseTimeInMills()
+ < maxInstalledCacheDuration) {
+ continue;
+ }
+
+ boolean installedOnlyAsInstantApp = false;
+ for (int userId : allUsers) {
+ final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (userState.isInstalled()) {
+ if (userState.isInstantApp()) {
+ installedOnlyAsInstantApp = true;
} else {
- final PackageStateInternal lhsPs =
- mPmInternal.getPackageStateInternal(lhsPkg.getPackageName());
- if (lhsPs == null) {
- return 0;
- }
-
- final PackageStateInternal rhsPs =
- mPmInternal.getPackageStateInternal(rhsPkg.getPackageName());
- if (rhsPs == null) {
- return 0;
- }
-
- if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() >
- rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
- return 1;
- } else if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() <
- rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
- return -1;
- } else if (
- PackageStateUtils.getEarliestFirstInstallTime(lhsPs.getUserStates())
- > PackageStateUtils.getEarliestFirstInstallTime(
- rhsPs.getUserStates())) {
- return 1;
- } else {
- return -1;
- }
+ installedOnlyAsInstantApp = false;
+ break;
}
- });
+ }
+ }
+ if (installedOnlyAsInstantApp) {
+ if (packagesToDelete == null) {
+ packagesToDelete = new ArrayList<>();
+ }
+ packagesToDelete.add(pkg.getPackageName());
}
}
if (packagesToDelete != null) {
- final int packageCount = packagesToDelete.size();
- final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mService);
- for (int i = 0; i < packageCount; i++) {
- final String packageToDelete = packagesToDelete.get(i);
- if (deletePackageHelper.deletePackageX(packageToDelete,
- PackageManager.VERSION_CODE_HIGHEST,
- UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
- true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
- if (file.getUsableSpace() >= neededSpace) {
- return true;
+ packagesToDelete.sort((String lhs, String rhs) -> {
+ final PackageStateInternal lhsPkgState = packageStates.get(lhs);
+ final PackageStateInternal rhsPkgState = packageStates.get(rhs);
+ final AndroidPackage lhsPkg = lhsPkgState == null ? null : lhsPkgState.getPkg();
+ final AndroidPackage rhsPkg = rhsPkgState == null ? null : rhsPkgState.getPkg();
+ if (lhsPkg == null && rhsPkg == null) {
+ return 0;
+ } else if (lhsPkg == null) {
+ return -1;
+ } else if (rhsPkg == null) {
+ return 1;
+ } else {
+ final PackageStateInternal lhsPs =
+ packageStates.get(lhsPkg.getPackageName());
+ if (lhsPs == null) {
+ return 0;
+ }
+
+ final PackageStateInternal rhsPs =
+ packageStates.get(rhsPkg.getPackageName());
+ if (rhsPs == null) {
+ return 0;
+ }
+
+ if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() >
+ rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
+ return 1;
+ } else if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() <
+ rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
+ return -1;
+ } else if (
+ PackageStateUtils.getEarliestFirstInstallTime(lhsPs.getUserStates())
+ > PackageStateUtils.getEarliestFirstInstallTime(
+ rhsPs.getUserStates())) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ });
+ }
+
+ synchronized (mLock) {
+ if (packagesToDelete != null) {
+ final int packageCount = packagesToDelete.size();
+ for (int i = 0; i < packageCount; i++) {
+ final String packageToDelete = packagesToDelete.get(i);
+ if (mDeletePackageHelper.deletePackageX(packageToDelete,
+ PackageManager.VERSION_CODE_HIGHEST,
+ UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
+ if (file.getUsableSpace() >= neededSpace) {
+ return true;
+ }
}
}
}
- }
- // Prune uninstalled instant apps
- synchronized (mService.mLock) {
+ // Prune uninstalled instant apps
// TODO: Track last used time for uninstalled instant apps for better pruning
- for (int userId : UserManagerService.getInstance().getUserIds()) {
+ for (int userId : mUserManager.getUserIds()) {
// Prune in-memory state
removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
@@ -928,21 +938,19 @@
return false;
}
- @GuardedBy("mService.mLock")
- private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
- @UserIdInt int userId) {
+ private @Nullable List<InstantAppInfo> getInstalledInstantApplications(
+ @NonNull Computer computer, @UserIdInt int userId) {
List<InstantAppInfo> result = null;
- final int packageCount = mService.mPackages.size();
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ computer.getPackageStates();
+ final int packageCount = packageStates.size();
for (int i = 0; i < packageCount; i++) {
- final AndroidPackage pkg = mService.mPackages.valueAt(i);
- final PackageStateInternal ps =
- mPmInternal.getPackageStateInternal(pkg.getPackageName());
+ final PackageStateInternal ps = packageStates.valueAt(i);
if (ps == null || !ps.getUserStateOrDefault(userId).isInstantApp()) {
continue;
}
- final InstantAppInfo info = createInstantAppInfoForPackage(
- pkg, userId, true);
+ final InstantAppInfo info = createInstantAppInfoForPackage(ps, userId, true);
if (info == null) {
continue;
}
@@ -956,14 +964,10 @@
}
private @NonNull
- InstantAppInfo createInstantAppInfoForPackage(
- @NonNull AndroidPackage pkg, @UserIdInt int userId,
- boolean addApplicationInfo) {
- PackageStateInternal ps = mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
- return null;
- }
- if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ InstantAppInfo createInstantAppInfoForPackage(@NonNull PackageStateInternal ps,
+ @UserIdInt int userId, boolean addApplicationInfo) {
+ AndroidPackage pkg = ps.getPkg();
+ if (pkg == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
return null;
}
@@ -982,17 +986,18 @@
if (addApplicationInfo) {
return new InstantAppInfo(appInfo, requestedPermissions, grantedPermissions);
} else {
+ // TODO: PMS lock re-entry
return new InstantAppInfo(appInfo.packageName,
- appInfo.loadLabel(mService.mContext.getPackageManager()),
+ appInfo.loadLabel(mContext.getPackageManager()),
requestedPermissions, grantedPermissions);
}
}
- @GuardedBy("mService.mLock")
- private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
+ @Nullable
+ private List<InstantAppInfo> getUninstalledInstantApplications(@NonNull Computer computer,
@UserIdInt int userId) {
List<UninstalledInstantAppState> uninstalledAppStates =
- getUninstalledInstantAppStatesLPr(userId);
+ getUninstalledInstantAppStates(userId);
if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
return null;
}
@@ -1009,6 +1014,7 @@
return uninstalledApps;
}
+ @SuppressLint("MissingPermission")
private void propagateInstantAppPermissionsIfNeeded(@NonNull AndroidPackage pkg,
@UserIdInt int userId) {
InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
@@ -1025,8 +1031,9 @@
final boolean propagatePermission = canPropagatePermission(grantedPermission);
if (propagatePermission && pkg.getRequestedPermissions().contains(
grantedPermission)) {
- mService.grantRuntimePermission(pkg.getPackageName(), grantedPermission,
- userId);
+ mContext.getSystemService(PermissionManager.class)
+ .grantRuntimePermission(pkg.getPackageName(), grantedPermission,
+ UserHandle.of(userId));
}
}
} finally {
@@ -1035,8 +1042,8 @@
}
private boolean canPropagatePermission(@NonNull String permissionName) {
- final PermissionManager permissionManager = mService.mContext.getSystemService(
- PermissionManager.class);
+ final PermissionManager permissionManager =
+ mContext.getSystemService(PermissionManager.class);
final PermissionInfo permissionInfo = permissionManager.getPermissionInfo(permissionName,
0);
return permissionInfo != null
@@ -1050,16 +1057,19 @@
private @NonNull
InstantAppInfo peekOrParseUninstalledInstantAppInfo(
@NonNull String packageName, @UserIdInt int userId) {
- if (mUninstalledInstantApps != null) {
- List<UninstalledInstantAppState> uninstalledAppStates =
- mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates != null) {
- final int appCount = uninstalledAppStates.size();
- for (int i = 0; i < appCount; i++) {
- UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
- if (uninstalledAppState.mInstantAppInfo
- .getPackageName().equals(packageName)) {
- return uninstalledAppState.mInstantAppInfo;
+ synchronized (mLock) {
+ if (mUninstalledInstantApps != null) {
+ List<UninstalledInstantAppState> uninstalledAppStates =
+ mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates != null) {
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(
+ i);
+ if (uninstalledAppState.mInstantAppInfo
+ .getPackageName().equals(packageName)) {
+ return uninstalledAppState.mInstantAppInfo;
+ }
}
}
}
@@ -1075,14 +1085,15 @@
return uninstalledAppState.mInstantAppInfo;
}
- @GuardedBy("mService.mLock")
- private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
- @UserIdInt int userId) {
+ @Nullable
+ private List<UninstalledInstantAppState> getUninstalledInstantAppStates(@UserIdInt int userId) {
List<UninstalledInstantAppState> uninstalledAppStates = null;
- if (mUninstalledInstantApps != null) {
- uninstalledAppStates = mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates != null) {
- return uninstalledAppStates;
+ synchronized (mLock) {
+ if (mUninstalledInstantApps != null) {
+ uninstalledAppStates = mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates != null) {
+ return uninstalledAppStates;
+ }
}
}
@@ -1109,7 +1120,9 @@
}
}
- mUninstalledInstantApps.put(userId, uninstalledAppStates);
+ synchronized (mLock) {
+ mUninstalledInstantApps.put(userId, uninstalledAppStates);
+ }
return uninstalledAppStates;
}
@@ -1246,7 +1259,7 @@
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
- mService.mContext.getPackageManager()).toString());
+ mContext.getPackageManager()).toString());
serializer.startTag(null, TAG_PERMISSIONS);
for (String permission : instantApp.getRequestedPermissions()) {
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index db346da..afca350 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -31,6 +31,7 @@
import android.util.TypedXmlSerializer;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.WatchedArrayMap;
import org.xmlpull.v1.XmlPullParser;
@@ -353,9 +354,9 @@
return mKeySets.get(id) != null;
}
- public boolean shouldCheckUpgradeKeySetLocked(PackageSetting oldPs, int scanFlags) {
+ public boolean shouldCheckUpgradeKeySetLocked(PackageStateInternal oldPs, int scanFlags) {
// Can't rotate keys during boot or if sharedUser.
- if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+ if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || (oldPs.getSharedUser() != null)
|| !oldPs.getKeySetData().isUsingUpgradeKeySets()) {
return false;
}
@@ -374,7 +375,7 @@
return true;
}
- public boolean checkUpgradeKeySetLocked(PackageSetting oldPS, AndroidPackage pkg) {
+ public boolean checkUpgradeKeySetLocked(PackageStateInternal oldPS, AndroidPackage pkg) {
// Upgrade keysets are being used. Determine if new package has a superset of the
// required keys.
long[] upgradeKeySets = oldPS.getKeySetData().getUpgradeKeySets();
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index c125fe1..bd00914 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -30,6 +30,7 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.storage.StorageManager;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -376,12 +377,13 @@
}
// Make a copy of all packages and look into each package.
- final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
- final ArrayList<AndroidPackage> pkgs = new ArrayList<>();
- pmInt.forEachPackage(pkgs::add);
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ LocalServices.getService(PackageManagerInternal.class).getPackageStates();
int packagePaths = 0;
int pathsSuccessful = 0;
- for (AndroidPackage pkg : pkgs) {
+ for (int index = 0; index < packageStates.size(); index++) {
+ final PackageStateInternal packageState = packageStates.valueAt(index);
+ final AndroidPackage pkg = packageState.getPkg();
if (pkg == null) {
continue;
}
@@ -404,10 +406,9 @@
continue;
}
- PackageStateInternal pkgSetting = pmInt.getPackageStateInternal(pkg.getPackageName());
final String[] instructionSets = getAppDexInstructionSets(
- AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
- AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting));
+ AndroidPackageUtils.getPrimaryCpuAbi(pkg, packageState),
+ AndroidPackageUtils.getSecondaryCpuAbi(pkg, packageState));
final List<String> paths =
AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index a5b42f0..69d4987 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -182,7 +182,7 @@
mInjector = injector;
}
- boolean canOptimizePackage(AndroidPackage pkg) {
+ boolean canOptimizePackage(@NonNull AndroidPackage pkg) {
// We do not dexopt a package with no code.
// Note that the system package is marked as having no code, however we can
// still optimize it via dexoptSystemServerPath.
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index d1ea41a..ec71940 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -34,9 +34,7 @@
import static com.android.server.pm.PackageManagerService.POST_INSTALL;
import static com.android.server.pm.PackageManagerService.PRUNE_UNUSED_STATIC_SHARED_LIBRARIES;
import static com.android.server.pm.PackageManagerService.SEND_PENDING_BROADCAST;
-import static com.android.server.pm.PackageManagerService.SNAPSHOT_UNCORK;
import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerService.TRACE_SNAPSHOTS;
import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_LIST;
import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_RESTRICTIONS;
import static com.android.server.pm.PackageManagerService.WRITE_SETTINGS;
@@ -379,13 +377,6 @@
mPm.mDomainVerificationManager.runMessage(messageCode, object);
break;
}
- case SNAPSHOT_UNCORK: {
- int corking = mPm.sSnapshotCorked.decrementAndGet();
- if (TRACE_SNAPSHOTS && corking == 0) {
- Log.e(TAG, "snapshot: corking goes to zero in message handler");
- }
- break;
- }
case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: {
try {
mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ccc375f..8465248 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -84,7 +84,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
@@ -782,7 +782,7 @@
// If caller requested explicit location, validity check it, otherwise
// resolve the best internal or adopted location.
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- if (!PackageHelper.fitsOnInternal(mContext, params)) {
+ if (!InstallLocationUtils.fitsOnInternal(mContext, params)) {
throw new IOException("No suitable internal storage available");
}
} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
@@ -796,7 +796,7 @@
// requested install flags, delta size, and manifest settings.
final long ident = Binder.clearCallingIdentity();
try {
- params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
+ params.volumeUuid = InstallLocationUtils.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 390dd3f..7152783 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -143,8 +143,8 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.os.SomeArgs;
import com.android.internal.security.VerityUtils;
@@ -1537,7 +1537,7 @@
if (stageDir != null && lengthBytes > 0) {
mContext.getSystemService(StorageManager.class).allocateBytes(
targetPfd.getFileDescriptor(), lengthBytes,
- PackageHelper.translateAllocateFlags(params.installFlags));
+ InstallLocationUtils.translateAllocateFlags(params.installFlags));
}
if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e0d404a..ee5c638 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -101,6 +101,7 @@
import android.content.pm.ModuleInfo;
import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ComponentEnabledSetting;
@@ -110,7 +111,6 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.PackageManager.PropertyLocation;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
import android.content.pm.PackagePartitions;
import android.content.pm.ParceledListSlice;
@@ -130,6 +130,7 @@
import android.content.pm.VersionedPackage;
import android.content.pm.dex.IArtManager;
import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.PackageLite;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -190,7 +191,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.telephony.CarrierAppUtils;
import com.android.internal.util.ArrayUtils;
@@ -232,17 +233,21 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
+import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
import com.android.server.pm.pkg.mutate.PackageStateWrite;
+import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
import com.android.server.storage.DeviceStorageMonitorInternal;
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.Watchable;
@@ -592,12 +597,12 @@
// the suffix "Locked". Some methods may use the legacy suffix "LP"
final PackageManagerTracedLock mLock;
+ // Ensures order of overlay updates until data storage can be moved to overlay code
+ private final PackageManagerTracedLock mOverlayPathsLock = new PackageManagerTracedLock();
+
// Lock alias for doing package state mutation
private final PackageManagerTracedLock mPackageStateWriteLock;
- // Lock alias to track syncing a consistent Computer
- private final PackageManagerTracedLock mLiveComputerSyncLock;
-
private final PackageStateMutator mPackageStateMutator = new PackageStateMutator(
this::getPackageSettingForMutation,
this::getDisabledPackageSettingForMutation);
@@ -682,23 +687,11 @@
@Watched
final InstantAppRegistry mInstantAppRegistry;
- @GuardedBy("mLock")
- int mChangedPackagesSequenceNumber;
- /**
- * List of changed [installed, removed or updated] packages.
- * mapping from user id -> sequence number -> package name
- */
- @GuardedBy("mLock")
- final SparseArray<SparseArray<String>> mChangedPackages = new SparseArray<>();
- /**
- * The sequence number of the last change to a package.
- * mapping from user id -> package name -> sequence number
- */
- @GuardedBy("mLock")
- final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+ @NonNull
+ final ChangedPackagesTracker mChangedPackagesTracker;
- @GuardedBy("mLock")
- final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+ @NonNull
+ private final PackageObserverHelper mPackageObserverHelper = new PackageObserverHelper();
private final ModuleInfoProvider mModuleInfoProvider;
@@ -736,21 +729,11 @@
ApplicationPackageManager.invalidateGetPackagesForUidCache();
ApplicationPackageManager.disableGetPackagesForUidCache();
ApplicationPackageManager.invalidateHasSystemFeatureCache();
-
- // Avoid invalidation-thrashing by preventing cache invalidations from causing property
- // writes if the cache isn't enabled yet. We re-enable writes later when we're
- // done initializing.
- sSnapshotCorked.incrementAndGet();
PackageManager.corkPackageInfoCache();
}
@Override
public void enablePackageCaches() {
- // Uncork cache invalidations and allow clients to cache package information.
- int corking = sSnapshotCorked.decrementAndGet();
- if (TRACE_SNAPSHOTS && corking == 0) {
- Log.i(TAG, "snapshot: corking returns to 0");
- }
PackageManager.uncorkPackageInfoCache();
}
}
@@ -866,8 +849,10 @@
@Watched
final ComponentResolver mComponentResolver;
- // List of packages names to keep cached, even if they are uninstalled for all users
- private List<String> mKeepUninstalledPackages;
+ // Set of packages names to keep cached, even if they are uninstalled for all users
+ @GuardedBy("mKeepUninstalledPackages")
+ @NonNull
+ private final ArraySet<String> mKeepUninstalledPackages = new ArraySet<>();
// Cached reference to IDevicePolicyManager.
private IDevicePolicyManager mDevicePolicyManager = null;
@@ -904,8 +889,7 @@
static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
static final int DOMAIN_VERIFICATION = 27;
- static final int SNAPSHOT_UNCORK = 28;
- static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 29;
+ static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -951,6 +935,7 @@
final @Nullable String mOverlayConfigSignaturePackage;
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
+ private final @NonNull String mRequiredSupplementalProcessPackage;
@GuardedBy("mLock")
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -1018,7 +1003,8 @@
isolatedOwners = mIsolatedOwnersSnapshot.snapshot();
packages = mPackagesSnapshot.snapshot();
instrumentation = mInstrumentationSnapshot.snapshot();
- resolveComponentName = mResolveComponentName.clone();
+ resolveComponentName = mResolveComponentName == null
+ ? null : mResolveComponentName.clone();
resolveActivity = new ActivityInfo(mResolveActivity);
instantAppInstallerActivity =
(mInstantAppInstallerActivity == null)
@@ -1075,10 +1061,6 @@
// should only be set true while holding mLock. However, the attribute id guaranteed
// to be set false only while mLock and mSnapshotLock are both held.
private static final AtomicBoolean sSnapshotInvalid = new AtomicBoolean(true);
- // If true, the snapshot is corked. Do not create a new snapshot but use the live
- // computer. This throttles snapshot creation during periods of churn in Package
- // Manager.
- static final AtomicInteger sSnapshotCorked = new AtomicInteger(0);
static final ThreadLocal<ThreadComputer> sThreadComputer =
ThreadLocal.withInitial(ThreadComputer::new);
@@ -1095,16 +1077,9 @@
* The snapshot statistics. These are collected to track performance and to identify
* situations in which the snapshots are misbehaving.
*/
+ @Nullable
private final SnapshotStatistics mSnapshotStatistics;
- // The snapshot disable/enable switch. An image with the flag set true uses snapshots
- // and an image with the flag set false does not use snapshots.
- private static final boolean SNAPSHOT_ENABLED = true;
-
- // The per-instance snapshot disable/enable flag. This is generally set to false in
- // test instances and set to SNAPSHOT_ENABLED in operational instances.
- private final boolean mSnapshotEnabled;
-
/**
* Return the live computer.
*/
@@ -1117,17 +1092,10 @@
* The live computer will be returned if snapshots are disabled.
*/
Computer snapshotComputer() {
- if (!mSnapshotEnabled) {
- return mLiveComputer;
- }
if (Thread.holdsLock(mLock)) {
// If the current thread holds mLock then it may have modified state but not
// yet invalidated the snapshot. Always give the thread the live computer.
return mLiveComputer;
- } else if (sSnapshotCorked.get() > 0) {
- // Snapshots are corked, which means new ones should not be built right now.
- mSnapshotStatistics.corked();
- return mLiveComputer;
}
synchronized (mSnapshotLock) {
// This synchronization block serializes access to the snapshot computer and
@@ -1168,7 +1136,9 @@
mSnapshotComputer = new ComputerEngine(args);
final long done = SystemClock.currentTimeMicro();
- mSnapshotStatistics.rebuild(now, done, hits);
+ if (mSnapshotStatistics != null) {
+ mSnapshotStatistics.rebuild(now, done, hits);
+ }
}
/**
@@ -1397,32 +1367,41 @@
}
}
- void scheduleWritePackageRestrictionsLocked(UserHandle user) {
+ void scheduleWritePackageRestrictions(UserHandle user) {
final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
}
- void scheduleWritePackageRestrictionsLocked(int userId) {
+ void scheduleWritePackageRestrictions(int userId) {
invalidatePackageInfoCache();
- final int[] userIds = (userId == UserHandle.USER_ALL)
- ? mUserManager.getUserIds() : new int[]{userId};
- for (int nextUserId : userIds) {
- if (!mUserManager.exists(nextUserId)) return;
-
- mDirtyUsers.add(nextUserId);
- if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
- mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
+ if (userId == UserHandle.USER_ALL) {
+ synchronized (mDirtyUsers) {
+ for (int aUserId : mUserManager.getUserIds()) {
+ mDirtyUsers.add(aUserId);
+ }
}
+ } else {
+ if (!mUserManager.exists(userId)) {
+ return;
+ }
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.add(userId);
+ }
+ }
+ if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
+ mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
}
}
void writePendingRestrictions() {
synchronized (mLock) {
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
- for (int userId : mDirtyUsers) {
- mSettings.writePackageRestrictionsLPr(userId);
+ synchronized (mDirtyUsers) {
+ for (int userId : mDirtyUsers) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ mDirtyUsers.clear();
}
- mDirtyUsers.clear();
}
}
@@ -1431,7 +1410,9 @@
mHandler.removeMessages(WRITE_SETTINGS);
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP();
- mDirtyUsers.clear();
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.clear();
+ }
}
}
@@ -1523,25 +1504,19 @@
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest,
PackagePartitions.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG,
- Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED);
+ Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL);
t.traceEnd(); // "create package manager"
final CompatChange.ChangeListener selinuxChangeListener = packageName -> {
synchronized (m.mInstallLock) {
- final AndroidPackage pkg;
- final PackageSetting ps;
- final SharedUserSetting sharedUser;
- final String oldSeInfo;
- synchronized (m.mLock) {
- ps = m.mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Slog.e(TAG, "Failed to find package setting " + packageName);
- return;
- }
- pkg = ps.getPkg();
- sharedUser = ps.getSharedUser();
- oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ final PackageStateInternal packageState = m.getPackageStateInternal(packageName);
+ if (packageState == null) {
+ Slog.e(TAG, "Failed to find package setting " + packageName);
+ return;
}
+ AndroidPackage pkg = packageState.getPkg();
+ SharedUserApi sharedUser = packageState.getSharedUser();
+ String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
if (pkg == null) {
Slog.e(TAG, "Failed to find package " + packageName);
@@ -1553,7 +1528,8 @@
if (!newSeInfo.equals(oldSeInfo)) {
Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
+ oldSeInfo + " to: " + newSeInfo);
- ps.getPkgState().setOverrideSeInfo(newSeInfo);
+ m.commitPackageStateMutation(null, packageName,
+ state -> state.setOverrideSeInfo(newSeInfo));
m.mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
}
}
@@ -1573,31 +1549,51 @@
/** Install/uninstall system packages for all users based on their user-type, as applicable. */
private void installAllowlistedSystemPackages() {
- synchronized (mLock) {
- final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
- isFirstBoot(), isDeviceUpgrading(), mExistingPackages);
- if (scheduleWrite) {
- scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL);
- scheduleWriteSettings();
- }
+ if (mUserManager.installWhitelistedSystemPackages(isFirstBoot(), isDeviceUpgrading(),
+ mExistingPackages)) {
+ scheduleWritePackageRestrictions(UserHandle.USER_ALL);
+ scheduleWriteSettings();
}
}
// Link watchables to the class
- private void registerObserver() {
- mPackages.registerObserver(mWatcher);
- mSharedLibraries.registerObserver(mWatcher);
- mInstrumentation.registerObserver(mWatcher);
- mWebInstantAppsDisabled.registerObserver(mWatcher);
- mAppsFilter.registerObserver(mWatcher);
- mInstantAppRegistry.registerObserver(mWatcher);
- mSettings.registerObserver(mWatcher);
- mIsolatedOwners.registerObserver(mWatcher);
- mComponentResolver.registerObserver(mWatcher);
- mFrozenPackages.registerObserver(mWatcher);
- // If neither "build" attribute is true then this may be a mockito test, and verification
- // can fail as a false positive.
- Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+ private void registerObservers(boolean verify) {
+ // Null check to handle nullable test parameters
+ if (mPackages != null) {
+ mPackages.registerObserver(mWatcher);
+ }
+ if (mSharedLibraries != null) {
+ mSharedLibraries.registerObserver(mWatcher);
+ }
+ if (mInstrumentation != null) {
+ mInstrumentation.registerObserver(mWatcher);
+ }
+ if (mWebInstantAppsDisabled != null) {
+ mWebInstantAppsDisabled.registerObserver(mWatcher);
+ }
+ if (mAppsFilter != null) {
+ mAppsFilter.registerObserver(mWatcher);
+ }
+ if (mInstantAppRegistry != null) {
+ mInstantAppRegistry.registerObserver(mWatcher);
+ }
+ if (mSettings != null) {
+ mSettings.registerObserver(mWatcher);
+ }
+ if (mIsolatedOwners != null) {
+ mIsolatedOwners.registerObserver(mWatcher);
+ }
+ if (mComponentResolver != null) {
+ mComponentResolver.registerObserver(mWatcher);
+ }
+ if (mFrozenPackages != null) {
+ mFrozenPackages.registerObserver(mWatcher);
+ }
+ if (verify) {
+ // If neither "build" attribute is true then this may be a mockito test,
+ // and verification can fail as a false positive.
+ Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+ }
}
/**
@@ -1619,7 +1615,6 @@
mInstallLock = injector.getInstallLock();
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
- mLiveComputerSyncLock = mLock;
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mUserManager = injector.getUserManagerService();
@@ -1639,6 +1634,7 @@
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
mInstantAppRegistry = testParams.instantAppRegistry;
+ mChangedPackagesTracker = testParams.changedPackagesTracker;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
@@ -1677,11 +1673,8 @@
mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
+ mRequiredSupplementalProcessPackage = testParams.requiredSupplementalProcessPackage;
- // Disable snapshots in this instance of PackageManagerService, which is only used
- // for testing. The instance still needs a live computer. The snapshot computer
- // is set to null since it must never be used by this instance.
- mSnapshotEnabled = false;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
mSnapshotStatistics = null;
@@ -1705,15 +1698,16 @@
mResolveIntentHelper = testParams.resolveIntentHelper;
mDexOptHelper = testParams.dexOptHelper;
mSuspendPackageHelper = testParams.suspendPackageHelper;
+
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
+ registerObservers(false);
invalidatePackageInfoCache();
}
public PackageManagerService(PackageManagerServiceInjector injector, boolean onlyCore,
boolean factoryTest, final String buildFingerprint, final boolean isEngBuild,
- final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion,
- boolean snapshotEnabled) {
+ final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) {
mIsEngBuild = isEngBuild;
mIsUserDebugBuild = isUserDebugBuild;
mSdkVersion = sdkVersion;
@@ -1728,7 +1722,6 @@
mInjector.bootstrap(this);
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
- mLiveComputerSyncLock = mLock;
mInstallLock = injector.getInstallLock();
LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -1832,7 +1825,10 @@
mApexManager = injector.getApexManager();
mAppsFilter = mInjector.getAppsFilter();
- mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager, mPmInternal);
+ mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
+ mInjector.getUserManagerInternal(), new DeletePackageHelper(this));
+
+ mChangedPackagesTracker = new ChangedPackagesTracker();
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
@@ -1857,16 +1853,12 @@
synchronized (mLock) {
// Create the computer as soon as the state objects have been installed. The
// cached computer is the same as the live computer until the end of the
- // constructor, at which time the invalidation method updates it. The cache is
- // corked initially to ensure a cached computer is not built until the end of the
- // constructor.
+ // constructor, at which time the invalidation method updates it.
mSnapshotStatistics = new SnapshotStatistics();
- sSnapshotCorked.set(1);
sSnapshotInvalid.set(true);
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
- mSnapshotEnabled = snapshotEnabled;
- registerObserver();
+ registerObservers(true);
}
// CHECKSTYLE:OFF IndentationCheck
@@ -2150,6 +2142,9 @@
getPackageInfo(mRequiredPermissionControllerPackage, 0,
UserHandle.USER_SYSTEM).getLongVersionCode());
+ // Resolve the supplemental process
+ mRequiredSupplementalProcessPackage = getRequiredSupplementalProcessPackageName();
+
// Initialize InstantAppRegistry's Instant App list for all users.
for (AndroidPackage pkg : mPackages.values()) {
if (pkg.isSystem()) {
@@ -2161,7 +2156,7 @@
|| !ps.getUserStateOrDefault(userId).isInstalled()) {
continue;
}
- mInstantAppRegistry.addInstantAppLPw(userId, ps.getAppId());
+ mInstantAppRegistry.addInstantApp(userId, ps.getAppId());
}
}
@@ -2852,11 +2847,17 @@
// TODO: Implement
// 7. Consider installed instant apps unused longer than min cache period
- if (internalVolume && mInstantAppRegistry.pruneInstalledInstantApps(bytes,
- android.provider.Settings.Global.getLong(mContext.getContentResolver(),
- Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
- InstantAppRegistry.DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
- return;
+ if (internalVolume) {
+ if (executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
+ android.provider.Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+ InstantAppRegistry
+ .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
+ ) {
+ return;
+ }
}
// 8. Consider cached app data (below quotas)
@@ -2873,11 +2874,17 @@
// TODO: Implement
// 10. Consider instant meta-data (uninstalled apps) older that min cache period
- if (internalVolume && mInstantAppRegistry.pruneUninstalledInstantApps(bytes,
- android.provider.Settings.Global.getLong(mContext.getContentResolver(),
- Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
- InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
- return;
+ if (internalVolume) {
+ if (executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
+ android.provider.Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+ InstantAppRegistry
+ .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
+ ) {
+ return;
+ }
}
// 11. Free storage service cache
@@ -2903,6 +2910,36 @@
throw new IOException("Failed to free " + bytes + " on storage device at " + file);
}
+ int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite,
+ String resolvedPath, String mPackageAbiOverride, int installFlags) {
+ // TODO: focus freeing disk space on the target device
+ final StorageManager storage = StorageManager.from(mContext);
+ final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());
+
+ final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath,
+ mPackageAbiOverride);
+ if (sizeBytes >= 0) {
+ synchronized (mInstallLock) {
+ try {
+ mInstaller.freeCache(null, sizeBytes + lowThreshold, 0);
+ PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo(
+ mContext, pkgLite, resolvedPath, installFlags,
+ mPackageAbiOverride);
+ // The cache free must have deleted the file we downloaded to install.
+ if (pkgInfoLite.recommendedInstallLocation
+ == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) {
+ pkgInfoLite.recommendedInstallLocation =
+ InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+ }
+ return pkgInfoLite.recommendedInstallLocation;
+ } catch (Installer.InstallerException e) {
+ Slog.w(TAG, "Failed to free cache", e);
+ }
+ }
+ }
+ return recommendedInstallLocation;
+ }
+
/**
* Update given flags when being used to request {@link PackageInfo}.
*/
@@ -3041,26 +3078,7 @@
@GuardedBy("mLock")
void updateSequenceNumberLP(PackageSetting pkgSetting, int[] userList) {
- for (int i = userList.length - 1; i >= 0; --i) {
- final int userId = userList[i];
- SparseArray<String> changedPackages = mChangedPackages.get(userId);
- if (changedPackages == null) {
- changedPackages = new SparseArray<>();
- mChangedPackages.put(userId, changedPackages);
- }
- Map<String, Integer> sequenceNumbers = mChangedPackagesSequenceNumbers.get(userId);
- if (sequenceNumbers == null) {
- sequenceNumbers = new HashMap<>();
- mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
- }
- final Integer sequenceNumber = sequenceNumbers.get(pkgSetting.getPackageName());
- if (sequenceNumber != null) {
- changedPackages.remove(sequenceNumber);
- }
- changedPackages.put(mChangedPackagesSequenceNumber, pkgSetting.getPackageName());
- sequenceNumbers.put(pkgSetting.getPackageName(), mChangedPackagesSequenceNumber);
- }
- mChangedPackagesSequenceNumber++;
+ mChangedPackagesTracker.updateSequenceNumber(pkgSetting.getPackageName(), userList);
}
@Override
@@ -3073,30 +3091,21 @@
return null;
}
enforceCrossUserPermission(callingUid, userId, false, false, "getChangedPackages");
- synchronized (mLock) {
- if (sequenceNumber >= mChangedPackagesSequenceNumber) {
- return null;
- }
- final SparseArray<String> changedPackages = mChangedPackages.get(userId);
- if (changedPackages == null) {
- return null;
- }
- final List<String> packageNames =
- new ArrayList<>(mChangedPackagesSequenceNumber - sequenceNumber);
- for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
- final String packageName = changedPackages.get(i);
- if (packageName != null) {
- // Filter out the changes if the calling package should not be able to see it.
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplication(ps, callingUid, userId)) {
- continue;
- }
- packageNames.add(packageName);
+ final ChangedPackages changedPackages = mChangedPackagesTracker.getChangedPackages(
+ sequenceNumber, userId);
+
+ if (changedPackages != null) {
+ final List<String> packageNames = changedPackages.getPackageNames();
+ for (int index = packageNames.size() - 1; index >= 0; index--) {
+ // Filter out the changes if the calling package should not be able to see it.
+ final PackageSetting ps = mSettings.getPackageLPr(packageNames.get(index));
+ if (shouldFilterApplication(ps, callingUid, userId)) {
+ packageNames.remove(index);
}
}
- return packageNames.isEmpty()
- ? null : new ChangedPackages(mChangedPackagesSequenceNumber, packageNames);
}
+
+ return changedPackages;
}
@Override
@@ -3143,14 +3152,19 @@
@Override
public String getPermissionControllerPackageName() {
final int callingUid = Binder.getCallingUid();
- if (mComputer.isPackageStateAvailableAndVisible(mRequiredPermissionControllerPackage,
- callingUid, UserHandle.getUserId(callingUid))) {
+ if (mComputer.getPackageStateFiltered(mRequiredPermissionControllerPackage,
+ callingUid, UserHandle.getUserId(callingUid)) != null) {
return mRequiredPermissionControllerPackage;
}
throw new IllegalStateException("PermissionController is not found");
}
+ @Override
+ public String getSupplementalProcessPackageName() {
+ return mRequiredSupplementalProcessPackage;
+ }
+
String getPackageInstallerPackageName() {
return mRequiredInstallerPackage;
}
@@ -3533,12 +3547,11 @@
}
enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
false /* checkShell */, "getEphemeralApplications");
- synchronized (mLock) {
- List<InstantAppInfo> instantApps = mInstantAppRegistry
- .getInstantAppsLPr(userId);
- if (instantApps != null) {
- return new ParceledListSlice<>(instantApps);
- }
+
+ List<InstantAppInfo> instantApps = executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.getInstantApps(computer, userId));
+ if (instantApps != null) {
+ return new ParceledListSlice<>(instantApps);
}
return null;
}
@@ -3565,10 +3578,11 @@
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
return null;
}
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppCookieLPw(
- packageName, userId);
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return null;
}
+ return mInstantAppRegistry.getInstantAppCookie(packageState.getPkg(), userId);
}
@Override
@@ -3582,10 +3596,13 @@
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
return false;
}
- synchronized (mLock) {
- return mInstantAppRegistry.setInstantAppCookieLPw(
- packageName, cookie, userId);
+
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return false;
}
+ return mInstantAppRegistry.setInstantAppCookie(packageState.getPkg(), cookie,
+ mContext.getPackageManager().getInstantAppCookieMaxBytes(), userId);
}
@Override
@@ -3601,10 +3618,7 @@
enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
false /* checkShell */, "getInstantAppIcon");
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppIconLPw(
- packageName, userId);
- }
+ return mInstantAppRegistry.getInstantAppIcon(packageName, userId);
}
boolean isCallerSameApp(String packageName, int uid) {
@@ -3706,7 +3720,7 @@
// Before everything else, see whether we need to fstrim.
try {
- IStorageManager sm = PackageHelper.getStorageManager();
+ IStorageManager sm = InstallLocationUtils.getStorageManager();
if (sm != null) {
boolean doTrim = false;
final long interval = android.provider.Settings.Global.getLong(
@@ -3722,16 +3736,14 @@
}
}
if (doTrim) {
- final boolean dexOptDialogShown;
- synchronized (mLock) {
- dexOptDialogShown = mDexOptHelper.isDexOptDialogShown();
- }
- if (!isFirstBoot() && dexOptDialogShown) {
- try {
- ActivityManager.getService().showBootMessage(
- mContext.getResources().getString(
- R.string.android_upgrading_fstrim), true);
- } catch (RemoteException e) {
+ if (!isFirstBoot()) {
+ if (mDexOptHelper.isDexOptDialogShown()) {
+ try {
+ ActivityManager.getService().showBootMessage(
+ mContext.getResources().getString(
+ R.string.android_upgrading_fstrim), true);
+ } catch (RemoteException e) {
+ }
}
}
sm.runMaintenance();
@@ -3751,29 +3763,27 @@
@Override
public void notifyPackageUse(String packageName, int reason) {
- synchronized (mLock) {
- final int callingUid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(callingUid);
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ boolean notify = executeWithConsistentComputerReturning(computer -> {
if (getInstantAppPackageName(callingUid) != null) {
- if (!isCallerSameApp(packageName, callingUid)) {
- return;
- }
+ return isCallerSameApp(packageName, callingUid);
} else {
- if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) {
- return;
- }
+ return !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID);
}
- notifyPackageUseLocked(packageName, reason);
- }
- }
-
- @GuardedBy("mLock")
- private void notifyPackageUseLocked(String packageName, int reason) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null) {
+ });
+ if (!notify) {
return;
}
- pkgSetting.getPkgState().setLastPackageUsageTimeInMills(reason, System.currentTimeMillis());
+
+ notifyPackageUseInternal(packageName, reason);
+ }
+
+ private void notifyPackageUseInternal(String packageName, int reason) {
+ long time = System.currentTimeMillis();
+ commitPackageStateMutation(null, packageName, packageState -> {
+ packageState.setLastPackageUsageTime(reason, time);
+ });
}
@Override
@@ -3899,10 +3909,12 @@
// This is the last chance to write out pending restriction settings
if (mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
- for (int userId : mDirtyUsers) {
- mSettings.writePackageRestrictionsLPr(userId);
+ synchronized (mDirtyUsers) {
+ for (int userId : mDirtyUsers) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ mDirtyUsers.clear();
}
- mDirtyUsers.clear();
}
}
}
@@ -3918,12 +3930,9 @@
throw new SecurityException("dumpProfiles");
}
- AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
+ AndroidPackage pkg = getPackage(packageName);
+ if (pkg == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
synchronized (mInstallLock) {
@@ -3946,14 +3955,12 @@
public Property getProperty(String propertyName, String packageName, String className) {
Objects.requireNonNull(propertyName);
Objects.requireNonNull(packageName);
- synchronized (mLock) {
- final PackageStateInternal ps = getPackageStateInternal(packageName);
- if (shouldFilterApplication(ps, Binder.getCallingUid(),
- UserHandle.getCallingUserId())) {
- return null;
- }
- return mPackageProperty.getProperty(propertyName, packageName, className);
+ PackageStateInternal packageState = mComputer.getPackageStateFiltered(packageName,
+ Binder.getCallingUid(), UserHandle.getCallingUserId());
+ if (packageState == null) {
+ return null;
}
+ return mPackageProperty.getProperty(propertyName, packageName, className);
}
@Override
@@ -4038,66 +4045,33 @@
@Override
public void notifyPackageAdded(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageAdded(packageName, uid);
- }
+ mPackageObserverHelper.notifyAdded(packageName, uid);
}
@Override
public void notifyPackageChanged(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageChanged(packageName, uid);
- }
+ mPackageObserverHelper.notifyChanged(packageName, uid);
}
@Override
public void notifyPackageRemoved(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageRemoved(packageName, uid);
- }
+ mPackageObserverHelper.notifyRemoved(packageName, uid);
}
- void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
+ void sendPackageAddedForUser(String packageName, @NonNull PackageStateInternal packageState,
int userId, int dataLoaderType) {
- final boolean isSystem = PackageManagerServiceUtils.isSystemApp(pkgSetting)
- || PackageManagerServiceUtils.isUpdatedSystemApp(pkgSetting);
- final boolean isInstantApp = pkgSetting.getInstantApp(userId);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ final boolean isSystem = packageState.isSystem();
+ final boolean isInstantApp = userState.isInstantApp();
final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
sendPackageAddedForNewUsers(packageName, isSystem /*sendBootCompleted*/,
- false /*startReceiver*/, pkgSetting.getAppId(), userIds, instantUserIds,
+ false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
dataLoaderType);
// Send a session commit broadcast
final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
- info.installReason = pkgSetting.getInstallReason(userId);
+ info.installReason = userState.getInstallReason();
info.appPackageName = packageName;
sendSessionCommitBroadcast(info, userId);
}
@@ -4129,7 +4103,6 @@
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
- PackageSetting pkgSetting;
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
true /* checkShell */, "setApplicationHiddenSetting for user " + userId);
@@ -4139,74 +4112,70 @@
return false;
}
+ // Do not allow "android" is being disabled
+ if ("android".equals(packageName)) {
+ Slog.w(TAG, "Cannot hide package: android");
+ return false;
+ }
+
final long callingId = Binder.clearCallingIdentity();
try {
- boolean sendAdded = false;
- boolean sendRemoved = false;
- // writer
- synchronized (mLock) {
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null) {
- return false;
- }
- if (shouldFilterApplication(pkgSetting, callingUid, userId)) {
- return false;
- }
- // Do not allow "android" is being disabled
- if ("android".equals(packageName)) {
- Slog.w(TAG, "Cannot hide package: android");
- return false;
- }
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- // Cannot hide SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
- Slog.w(TAG, "Cannot hide package: " + packageName
- + " providing SDK library: "
- + pkg.getSdkLibName());
- return false;
- }
- // Cannot hide static shared libs as they are considered
- // a part of the using app (emulating static linking). Also
- // static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
- Slog.w(TAG, "Cannot hide package: " + packageName
- + " providing static shared library: "
- + pkg.getStaticSharedLibName());
- return false;
- }
- }
- // Only allow protected packages to hide themselves.
- if (hidden && !UserHandle.isSameApp(callingUid, pkgSetting.getAppId())
- && mProtectedPackages.isPackageStateProtected(userId, packageName)) {
- Slog.w(TAG, "Not hiding protected package: " + packageName);
- return false;
- }
+ final PackageStateInternal packageState =
+ mComputer.getPackageStateFiltered(packageName, callingUid, userId);
+ if (packageState == null) {
+ return false;
+ }
- if (pkgSetting.getHidden(userId) != hidden) {
- pkgSetting.setHidden(hidden, userId);
- mSettings.writePackageRestrictionsLPr(userId);
- if (hidden) {
- sendRemoved = true;
- } else {
- sendAdded = true;
- }
+ // Cannot hide static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ AndroidPackage pkg = packageState.getPkg();
+ if (pkg != null) {
+ // Cannot hide SDK libs as they are controlled by SDK manager.
+ if (pkg.getSdkLibName() != null) {
+ Slog.w(TAG, "Cannot hide package: " + packageName
+ + " providing SDK library: "
+ + pkg.getSdkLibName());
+ return false;
+ }
+ // Cannot hide static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ if (pkg.getStaticSharedLibName() != null) {
+ Slog.w(TAG, "Cannot hide package: " + packageName
+ + " providing static shared library: "
+ + pkg.getStaticSharedLibName());
+ return false;
}
}
- if (sendAdded) {
- sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
- return true;
+ // Only allow protected packages to hide themselves.
+ if (hidden && !UserHandle.isSameApp(callingUid, packageState.getAppId())
+ && mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+ Slog.w(TAG, "Not hiding protected package: " + packageName);
+ return false;
}
- if (sendRemoved) {
- killApplication(packageName, pkgSetting.getAppId(), userId,
- "hiding pkg");
- sendApplicationHiddenForUser(packageName, pkgSetting, userId);
- return true;
+
+ if (packageState.getUserStateOrDefault(userId).isHidden() == hidden) {
+ return false;
}
+
+ commitPackageStateMutation(null, packageName, packageState1 ->
+ packageState1.userState(userId).setHidden(hidden));
+
+ final PackageStateInternal newPackageState = getPackageStateInternal(packageName);
+
+ if (hidden) {
+ killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg");
+ sendApplicationHiddenForUser(packageName, newPackageState, userId);
+ } else {
+ sendPackageAddedForUser(packageName, newPackageState, userId, DataLoaderType.NONE);
+ }
+
+ scheduleWritePackageRestrictions(userId);
+ return true;
} finally {
Binder.restoreCallingIdentity(callingId);
}
- return false;
}
@Override
@@ -4219,21 +4188,20 @@
"setSystemAppHiddenUntilInstalled");
}
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null || !pkgSetting.isSystem()) {
- return;
- }
- if (pkgSetting.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
- throw new SecurityException("Only system or phone callers can modify core apps");
- }
- pkgSetting.getPkgState().setHiddenUntilInstalled(hidden);
- final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(packageName);
- if (disabledPs == null) {
- return;
- }
- disabledPs.getPkgState().setHiddenUntilInstalled(hidden);
+ final PackageStateInternal stateRead = getPackageStateInternal(packageName);
+ if (stateRead == null || !stateRead.isSystem() || stateRead.getPkg() == null) {
+ return;
}
+ if (stateRead.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
+ throw new SecurityException("Only system or phone callers can modify core apps");
+ }
+
+ commitPackageStateMutation(null, mutator -> {
+ mutator.forPackage(packageName)
+ .setHiddenUntilInstalled(hidden);
+ mutator.forDisabledSystemPackage(packageName)
+ .setHiddenUntilInstalled(hidden);
+ });
}
@Override
@@ -4246,19 +4214,17 @@
"setSystemAppHiddenUntilInstalled");
}
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- // The target app should always be in system
- if (pkgSetting == null || !pkgSetting.isSystem()) {
- return false;
- }
- if (pkgSetting.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
- throw new SecurityException("Only system or phone callers can modify core apps");
- }
- // Check if the install state is the same
- if (pkgSetting.getInstalled(userId) == installed) {
- return false;
- }
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ // The target app should always be in system
+ if (packageState == null || !packageState.isSystem() || packageState.getPkg() == null) {
+ return false;
+ }
+ if (packageState.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
+ throw new SecurityException("Only system or phone callers can modify core apps");
+ }
+ // Check if the install state is the same
+ if (packageState.getUserStateOrDefault(userId).isInstalled() == installed) {
+ return false;
}
final long callingId = Binder.clearCallingIdentity();
@@ -4286,14 +4252,14 @@
}
}
- private void sendApplicationHiddenForUser(String packageName, PackageSetting pkgSetting,
+ private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState,
int userId) {
final PackageRemovedInfo info = new PackageRemovedInfo(this);
info.mRemovedPackage = packageName;
- info.mInstallerPackageName = pkgSetting.getInstallSource().installerPackageName;
+ info.mInstallerPackageName = packageState.getInstallSource().installerPackageName;
info.mRemovedUsers = new int[] {userId};
info.mBroadcastUsers = new int[] {userId};
- info.mUid = UserHandle.getUid(userId, pkgSetting.getAppId());
+ info.mUid = UserHandle.getUid(userId, packageState.getAppId());
info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
}
@@ -4348,45 +4314,52 @@
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
- final boolean[] canRestrict = (restrictionFlags != 0)
- ? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid)
- : null;
- for (int i = 0; i < packageNames.length; i++) {
- final String packageName = packageNames[i];
- final PackageSetting pkgSetting;
- synchronized (mLock) {
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null
- || shouldFilterApplication(pkgSetting, callingUid, userId)) {
+ ArraySet<String> changesToCommit = new ArraySet<>();
+ executeWithConsistentComputer(computer -> {
+ final boolean[] canRestrict = (restrictionFlags != 0)
+ ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId,
+ callingUid) : null;
+ for (int i = 0; i < packageNames.length; i++) {
+ final String packageName = packageNames[i];
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ if (packageState == null
+ || shouldFilterApplication(packageState, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageName
+ ". Skipping...");
unactionedPackages.add(packageName);
continue;
}
- }
- if (canRestrict != null && !canRestrict[i]) {
- unactionedPackages.add(packageName);
- continue;
- }
- synchronized (mLock) {
- final int oldDistractionFlags = pkgSetting.getDistractionFlags(userId);
+ if (canRestrict != null && !canRestrict[i]) {
+ unactionedPackages.add(packageName);
+ continue;
+ }
+ final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
+ .getDistractionFlags();
if (restrictionFlags != oldDistractionFlags) {
- pkgSetting.setDistractionFlags(restrictionFlags, userId);
changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ changesToCommit.add(packageName);
}
}
- }
+ });
+
+ commitPackageStateMutation(null, mutator -> {
+ final int size = changesToCommit.size();
+ for (int index = 0; index < size; index++) {
+ mutator.forPackage(changesToCommit.valueAt(index))
+ .userState(userId)
+ .setDistractionFlags(restrictionFlags);
+ }
+ });
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
changedPackages, changedUids.toArray(), userId, restrictionFlags));
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
+ scheduleWritePackageRestrictions(userId);
}
return unactionedPackages.toArray(new String[0]);
}
@@ -4450,10 +4423,9 @@
}
void unsuspendForSuspendingPackage(String suspendingPackage, int userId) {
- final String[] allPackages;
- synchronized (mLock) {
- allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
- }
+ // TODO: This can be replaced by a special parameter to iterate all packages, rather than
+ // this weird pre-collect of all packages.
+ final String[] allPackages = getPackageStates().keySet().toArray(new String[0]);
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
allPackages, suspendingPackage::equals, userId);
}
@@ -4479,22 +4451,27 @@
private void removeDistractingPackageRestrictions(String[] packagesToChange, int userId) {
final List<String> changedPackages = new ArrayList<>();
final IntArray changedUids = new IntArray();
- synchronized (mLock) {
- for (String packageName : packagesToChange) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null && ps.getDistractionFlags(userId) != 0) {
- ps.setDistractionFlags(0, userId);
- changedPackages.add(ps.getPackageName());
- changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
- }
+ for (String packageName : packagesToChange) {
+ final PackageStateInternal ps = getPackageStateInternal(packageName);
+ if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags() != 0) {
+ changedPackages.add(ps.getPackageName());
+ changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
}
- if (!changedPackages.isEmpty()) {
- final String[] packageArray = changedPackages.toArray(
- new String[changedPackages.size()]);
- mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
- packageArray, changedUids.toArray(), userId, 0));
- scheduleWritePackageRestrictionsLocked(userId);
+ }
+ commitPackageStateMutation(null, mutator -> {
+ for (int index = 0; index < changedPackages.size(); index++) {
+ mutator.forPackage(changedPackages.get(index))
+ .userState(userId)
+ .setDistractionFlags(0);
}
+ });
+
+ if (!changedPackages.isEmpty()) {
+ final String[] packageArray = changedPackages.toArray(
+ new String[changedPackages.size()]);
+ mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
+ packageArray, changedUids.toArray(), userId, 0));
+ scheduleWritePackageRestrictions(userId);
}
}
@@ -4616,43 +4593,40 @@
public void setInstallerPackageName(String targetPackage, String installerPackageName) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
- if (getInstantAppPackageName(callingUid) != null) {
- return;
- }
- // writer
- synchronized (mLock) {
- PackageSetting targetPackageSetting = mSettings.getPackageLPr(targetPackage);
- if (targetPackageSetting == null
- || shouldFilterApplication(
- targetPackageSetting, callingUid, callingUserId)) {
+ final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException>
+ implementation = computer -> {
+ if (computer.getInstantAppPackageName(callingUid) != null) {
+ return false;
+ }
+
+ PackageStateInternal targetPackageState =
+ computer.getPackageStateInternal(targetPackage);
+ if (targetPackageState == null
+ || computer.shouldFilterApplication(targetPackageState, callingUid,
+ callingUserId)) {
throw new IllegalArgumentException("Unknown target package: " + targetPackage);
}
- PackageSetting installerPackageSetting;
+ PackageStateInternal installerPackageState = null;
if (installerPackageName != null) {
- installerPackageSetting = mSettings.getPackageLPr(installerPackageName);
- if (installerPackageSetting == null
+ installerPackageState = computer.getPackageStateInternal(installerPackageName);
+ if (installerPackageState == null
|| shouldFilterApplication(
- installerPackageSetting, callingUid, callingUserId)) {
+ installerPackageState, callingUid, callingUserId)) {
throw new IllegalArgumentException("Unknown installer package: "
+ installerPackageName);
}
- } else {
- installerPackageSetting = null;
}
Signature[] callerSignature;
final int appId = UserHandle.getAppId(callingUid);
- final Object obj = mSettings.getSettingLPr(appId);
- if (obj != null) {
- if (obj instanceof SharedUserSetting) {
- callerSignature =
- ((SharedUserSetting) obj).signatures.mSigningDetails.getSignatures();
- } else if (obj instanceof PackageSetting) {
- callerSignature =
- ((PackageSetting) obj).getSigningDetails().getSignatures();
+ Pair<PackageStateInternal, SharedUserApi> either =
+ computer.getPackageOrSharedUser(appId);
+ if (either != null) {
+ if (either.first != null) {
+ callerSignature = either.first.getSigningDetails().getSignatures();
} else {
- throw new SecurityException("Bad object " + obj + " for uid " + callingUid);
+ callerSignature = either.second.getSigningDetails().getSignatures();
}
} else {
throw new SecurityException("Unknown calling UID: " + callingUid);
@@ -4660,22 +4634,22 @@
// Verify: can't set installerPackageName to a package that is
// not signed with the same cert as the caller.
- if (installerPackageSetting != null) {
+ if (installerPackageState != null) {
if (compareSignatures(callerSignature,
- installerPackageSetting.getSigningDetails().getSignatures())
+ installerPackageState.getSigningDetails().getSignatures())
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as new installer package "
- + installerPackageName);
+ + installerPackageName);
}
}
// Verify: if target already has an installer package, it must
// be signed with the same cert as the caller.
String targetInstallerPackageName =
- targetPackageSetting.getInstallSource().installerPackageName;
- PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null :
- mSettings.getPackageLPr(targetInstallerPackageName);
+ targetPackageState.getInstallSource().installerPackageName;
+ PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null
+ ? null : computer.getPackageStateInternal(targetInstallerPackageName);
if (targetInstallerPkgSetting != null) {
if (compareSignatures(callerSignature,
@@ -4683,10 +4657,10 @@
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as old installer package "
- + targetInstallerPackageName);
+ + targetInstallerPackageName);
}
} else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
- != PackageManager.PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED) {
// This is probably an attempt to exploit vulnerability b/150857253 of taking
// privileged installer permissions when the installer has been uninstalled or
// was never set.
@@ -4702,17 +4676,39 @@
+ Manifest.permission.INSTALL_PACKAGES);
} else {
// If change disabled, fail silently for backwards compatibility
- return;
+ return false;
}
} finally {
Binder.restoreCallingIdentity(binderToken);
}
}
- // Okay!
- targetPackageSetting.setInstallerPackageName(installerPackageName);
- mSettings.addInstallerPackageNames(targetPackageSetting.getInstallSource());
- mAppsFilter.addPackage(targetPackageSetting);
+ return true;
+ };
+ PackageStateMutator.InitialState initialState = recordInitialState();
+ boolean allowed = executeWithConsistentComputerReturningThrowing(implementation);
+ if (allowed) {
+ // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames,
+ // should find an alternative which avoids any race conditions
+ PackageStateInternal targetPackageState;
+ synchronized (mLock) {
+ PackageStateMutator.Result result = commitPackageStateMutation(initialState,
+ targetPackage, state -> state.setInstaller(installerPackageName));
+ if (result.isPackagesChanged() || result.isStateChanged()) {
+ synchronized (mPackageStateWriteLock) {
+ allowed = executeWithConsistentComputerReturningThrowing(implementation);
+ if (allowed) {
+ commitPackageStateMutation(null, targetPackage,
+ state -> state.setInstaller(installerPackageName));
+ } else {
+ return;
+ }
+ }
+ }
+ targetPackageState = getPackageStateInternal(targetPackage);
+ mSettings.addInstallerPackageNames(targetPackageState.getInstallSource());
+ }
+ mAppsFilter.addPackage(targetPackageState);
scheduleWriteSettings();
}
}
@@ -4725,21 +4721,42 @@
}
mInjector.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
callerPackageName);
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null || shouldFilterApplication(
- ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
+
+ final PackageStateMutator.InitialState initialState = recordInitialState();
+
+ final FunctionalUtils.ThrowingFunction<Computer, PackageStateMutator.Result>
+ implementation = computer -> {
+ PackageStateInternal packageState = computer.getPackageStateFiltered(packageName,
+ Binder.getCallingUid(), UserHandle.getCallingUserId());
+ if (packageState == null) {
throw new IllegalArgumentException("Unknown target package " + packageName);
}
- if (!Objects.equals(callerPackageName, ps.getInstallSource().installerPackageName)) {
+
+ if (!Objects.equals(callerPackageName,
+ packageState.getInstallSource().installerPackageName)) {
throw new IllegalArgumentException("Calling package " + callerPackageName
+ " is not installer for " + packageName);
}
- if (ps.getCategoryOverride() != categoryHint) {
- ps.setCategoryOverride(categoryHint);
- scheduleWriteSettings();
+ if (packageState.getCategoryOverride() != categoryHint) {
+ return commitPackageStateMutation(initialState,
+ packageName, state -> state.setCategoryOverride(categoryHint));
+ } else {
+ return null;
}
+ };
+
+ PackageStateMutator.Result result = executeWithConsistentComputerReturning(implementation);
+ if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) {
+ // TODO: Specific return value of what state changed?
+ // The installer on record might have changed, retry with lock
+ synchronized (mPackageStateWriteLock) {
+ result = executeWithConsistentComputerReturning(implementation);
+ }
+ }
+
+ if (result != null && result.isCommitted()) {
+ scheduleWriteSettings();
}
}
@@ -4792,32 +4809,6 @@
});
}
- // Stop gap method to allow mutating a package setting before commit on write is added
- private void mutateInstalledPackageSetting(@NonNull String packageName, int callingUid,
- @UserIdInt int userId,
- @NonNull FunctionalUtils.ThrowingConsumer<PackageSetting> consumerLocked) {
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not installed");
- return;
- }
- if (!ps.getInstalled(userId)) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not installed for user " + userId);
- return;
- }
- if (shouldFilterApplication(ps, callingUid, userId)) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not visible to the calling app");
- return;
- }
-
- consumerLocked.accept(ps);
- }
- }
-
void notifyPackageChangeObservers(PackageChangeEvent event) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "notifyPackageChangeObservers");
@@ -4872,9 +4863,8 @@
return mComputer.resolveExternalPackageName(pkg);
}
- @GuardedBy("mLock")
- String resolveInternalPackageNameLPr(String packageName, long versionCode) {
- return mComputer.resolveInternalPackageNameLPr(packageName, versionCode);
+ String resolveInternalPackageName(String packageName, long versionCode) {
+ return mComputer.resolveInternalPackageName(packageName, versionCode);
}
boolean isCallerVerifier(int callingUid) {
@@ -4947,28 +4937,29 @@
int userId) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- // TODO (b/157774108): This should fail on non-existent packages.
- synchronized (mLock) {
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- // Cannot block uninstall SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
- Slog.w(TAG, "Cannot block uninstall of package: " + packageName
- + " providing SDK library: " + pkg.getSdkLibName());
- return false;
- }
- // Cannot block uninstall of static shared libs as they are
- // considered a part of the using app (emulating static linking).
- // Also static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
- Slog.w(TAG, "Cannot block uninstall of package: " + packageName
- + " providing static shared library: " + pkg.getStaticSharedLibName());
- return false;
- }
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState != null && packageState.getPkg() != null) {
+ AndroidPackage pkg = packageState.getPkg();
+ // Cannot block uninstall SDK libs as they are controlled by SDK manager.
+ if (pkg.getSdkLibName() != null) {
+ Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+ + " providing SDK library: " + pkg.getSdkLibName());
+ return false;
}
- mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall);
- mSettings.writePackageRestrictionsLPr(userId);
+ // Cannot block uninstall of static shared libs as they are
+ // considered a part of the using app (emulating static linking).
+ // Also static libs are installed always on internal storage.
+ if (pkg.getStaticSharedLibName() != null) {
+ Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+ + " providing static shared library: " + pkg.getStaticSharedLibName());
+ return false;
+ }
}
+ synchronized (mLock) {
+ mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall);
+ }
+
+ scheduleWritePackageRestrictions(userId);
return true;
}
@@ -4978,24 +4969,17 @@
}
@Override
- public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp) {
+ public boolean setRequiredForSystemUser(String packageName, boolean requiredForSystemUser) {
PackageManagerServiceUtils.enforceSystemOrRoot(
"setRequiredForSystemUser can only be run by the system or root");
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Log.w(TAG, "Package doesn't exist: " + packageName);
- return false;
- }
- if (systemUserApp) {
- ps.setPrivateFlags(ps.getPrivateFlags()
- | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
- } else {
- ps.setPrivateFlags(ps.getPrivateFlags()
- & ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
- }
- writeSettingsLPrTEMP();
+
+ PackageStateMutator.Result result = commitPackageStateMutation(null, packageName,
+ packageState -> packageState.setRequiredForSystemUser(requiredForSystemUser));
+ if (!result.isCommitted()) {
+ return false;
}
+
+ scheduleWriteSettings();
return true;
}
@@ -5004,12 +4988,8 @@
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can clear all profile data");
- final AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- }
-
- try (PackageFreezer freezer = freezePackage(packageName, "clearApplicationProfileData")) {
+ final AndroidPackage pkg = getPackage(packageName);
+ try (PackageFreezer ignored = freezePackage(packageName, "clearApplicationProfileData")) {
synchronized (mInstallLock) {
mAppDataHelper.clearAppProfilesLIF(pkg);
}
@@ -5026,50 +5006,53 @@
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "clear application data");
- final boolean filterApp;
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- filterApp = shouldFilterApplication(ps, callingUid, userId);
+ if (mComputer.getPackageStateFiltered(packageName, callingUid, userId) == null) {
+ if (observer != null) {
+ mHandler.post(() -> {
+ try {
+ observer.onRemoveCompleted(packageName, false);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Observer no longer exists.");
+ }
+ });
+ }
+ return;
}
- if (!filterApp && mProtectedPackages.isPackageDataProtected(userId, packageName)) {
+ if (mProtectedPackages.isPackageDataProtected(userId, packageName)) {
throw new SecurityException("Cannot clear data for a protected package: "
+ packageName);
}
+
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
final boolean succeeded;
- if (!filterApp) {
- try (PackageFreezer freezer = freezePackage(packageName,
- "clearApplicationUserData")) {
- synchronized (mInstallLock) {
- succeeded = clearApplicationUserDataLIF(packageName, userId);
- }
- synchronized (mLock) {
- mInstantAppRegistry.deleteInstantApplicationMetadataLPw(
- packageName, userId);
- if (succeeded) {
- resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
- }
+ try (PackageFreezer freezer = freezePackage(packageName,
+ "clearApplicationUserData")) {
+ synchronized (mInstallLock) {
+ succeeded = clearApplicationUserDataLIF(packageName, userId);
+ }
+ mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId);
+ synchronized (mLock) {
+ if (succeeded) {
+ resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
}
}
- if (succeeded) {
- // invoke DeviceStorageMonitor's update method to clear any notifications
- DeviceStorageMonitorInternal dsm = LocalServices
- .getService(DeviceStorageMonitorInternal.class);
- if (dsm != null) {
- dsm.checkMemory();
- }
- if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
- == PERMISSION_GRANTED) {
- unsuspendForSuspendingPackage(packageName, userId);
- removeAllDistractingPackageRestrictions(userId);
- flushPackageRestrictionsAsUserInternalLocked(userId);
- }
+ }
+ if (succeeded) {
+ // invoke DeviceStorageMonitor's update method to clear any notifications
+ DeviceStorageMonitorInternal dsm = LocalServices
+ .getService(DeviceStorageMonitorInternal.class);
+ if (dsm != null) {
+ dsm.checkMemory();
}
- } else {
- succeeded = false;
+ if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
+ == PERMISSION_GRANTED) {
+ unsuspendForSuspendingPackage(packageName, userId);
+ removeAllDistractingPackageRestrictions(userId);
+ flushPackageRestrictionsAsUserInternalLocked(userId);
+ }
}
if (observer != null) {
try {
@@ -5089,17 +5072,7 @@
}
// Try finding details about the requested package
- AndroidPackage pkg;
- PackageSetting ps;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- ps = mSettings.getPackageLPr(packageName);
- if (pkg == null) {
- if (ps != null) {
- pkg = ps.getPkg();
- }
- }
- }
+ AndroidPackage pkg = getPackage(packageName);
if (pkg == null) {
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
@@ -5122,7 +5095,8 @@
} else {
flags = 0;
}
- mAppDataHelper.prepareAppDataContentsLIF(pkg, ps, userId, flags);
+ mAppDataHelper.prepareAppDataContentsLIF(pkg, getPackageStateInternal(packageName), userId,
+ flags);
return true;
}
@@ -5165,7 +5139,7 @@
updateSequenceNumberLP(pkgSetting, new int[] { userId });
updateInstantAppInstallerLocked(packageName);
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
final ArrayList<String> pendingComponents = mPendingBroadcasts.get(userId, packageName);
if (pendingComponents == null) {
@@ -5214,10 +5188,7 @@
final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_INSTANT_APPS);
- final AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- }
+ final AndroidPackage pkg = getPackage(packageName);
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(() -> {
@@ -5438,8 +5409,8 @@
}
}
resolver.addFilter(newFilter);
- scheduleWritePackageRestrictionsLocked(sourceUserId);
}
+ scheduleWritePackageRestrictions(sourceUserId);
}
@Override
@@ -5460,8 +5431,8 @@
resolver.removeFilter(filter);
}
}
- scheduleWritePackageRestrictionsLocked(sourceUserId);
}
+ scheduleWritePackageRestrictions(sourceUserId);
}
// Enforcing that callingUid is owning pkg on userId
@@ -5559,6 +5530,24 @@
}
}
+ private @NonNull String getRequiredSupplementalProcessPackageName() {
+ final Intent intent = new Intent(SupplementalProcessManagerLocal.SERVICE_INTERFACE);
+
+ final List<ResolveInfo> matches = queryIntentServicesInternal(
+ intent,
+ /* resolvedType= */ null,
+ MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.USER_SYSTEM,
+ /* callingUid= */ Process.myUid(),
+ /* includeInstantApps= */ false);
+ if (matches.size() == 1) {
+ return matches.get(0).getComponentInfo().packageName;
+ } else {
+ throw new RuntimeException("There should exactly one supplemental process; found "
+ + matches.size() + ": matches=" + matches);
+ }
+ }
+
@Override
public String getDefaultTextClassifierPackageName() {
return ensureSystemPackageName(
@@ -5733,12 +5722,8 @@
@Override
public void setUpdateAvailable(String packageName, boolean updateAvailable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting != null) {
- pkgSetting.setUpdateAvailable(updateAvailable);
- }
- }
+ commitPackageStateMutation(null, packageName, state ->
+ state.setUpdateAvailable(updateAvailable));
}
@Override
@@ -5763,32 +5748,32 @@
}
int callingUid = Binder.getCallingUid();
-
String componentPkgName = componentName.getPackageName();
- int componentUid = getPackageUid(componentPkgName, 0, userId);
- if (!UserHandle.isSameApp(callingUid, componentUid)) {
- throw new SecurityException("The calling UID (" + callingUid + ")"
- + " does not match the target UID");
- }
- String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage);
- if (TextUtils.isEmpty(allowedCallerPkg)) {
- throw new SecurityException(
- "There is no package defined as allowed to change a component's label or icon");
- }
+ boolean changed = executeWithConsistentComputerReturning(computer -> {
+ int componentUid = getPackageUid(componentPkgName, 0, userId);
+ if (!UserHandle.isSameApp(callingUid, componentUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " does not match the target UID");
+ }
- int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
- userId);
- if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
- throw new SecurityException("The calling UID (" + callingUid + ")"
- + " is not allowed to change a component's label or icon");
- }
+ String allowedCallerPkg =
+ mContext.getString(R.string.config_overrideComponentUiPackage);
+ if (TextUtils.isEmpty(allowedCallerPkg)) {
+ throw new SecurityException( "There is no package defined as allowed to change a "
+ + "component's label or icon");
+ }
- synchronized (mLock) {
- AndroidPackage pkg = mPackages.get(componentPkgName);
- PackageSetting pkgSetting = getPackageSettingForMutation(componentPkgName);
- if (pkg == null || pkgSetting == null
- || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) {
+ int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
+ userId);
+ if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " is not allowed to change a component's label or icon");
+ }
+ PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName);
+ if (packageState == null || packageState.getPkg() == null
+ || (!packageState.isSystem()
+ && !packageState.getTransientState().isUpdatedSystemApp())) {
throw new SecurityException(
"Changing the label is not allowed for " + componentName);
}
@@ -5797,13 +5782,23 @@
throw new IllegalArgumentException("Component " + componentName + " not found");
}
- if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel,
- icon, userId)) {
- // Nothing changed
- return;
- }
+ Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId)
+ .getOverrideLabelIconForComponent(componentName);
+
+ String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first;
+ Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second;
+
+ return !TextUtils.equals(existingLabel, nonLocalizedLabel)
+ || !Objects.equals(existingIcon, icon);
+ });
+ if (!changed) {
+ return;
}
+ commitPackageStateMutation(null, componentPkgName,
+ state -> state.userState(userId)
+ .setComponentLabelIcon(componentName, nonLocalizedLabel, icon));
+
ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName);
if (components == null) {
components = new ArrayList<>();
@@ -6076,7 +6071,7 @@
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
}
if (scheduleBroadcastMessage) {
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
@@ -6187,9 +6182,11 @@
// NOTE: this invokes synchronous disk access, so callers using this
// method should consider running on a background thread
mSettings.writePackageRestrictionsLPr(userId);
- mDirtyUsers.remove(userId);
- if (mDirtyUsers.isEmpty()) {
- mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.remove(userId);
+ if (mDirtyUsers.isEmpty()) {
+ mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+ }
}
}
@@ -6216,29 +6213,50 @@
public void setPackageStoppedState(String packageName, boolean stopped, int userId) {
if (!mUserManager.exists(userId)) return;
final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return;
- }
- final int permission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
- final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
- if (!allowedByPermission
- && !ArrayUtils.contains(getPackagesForUid(callingUid), packageName)) {
- throw new SecurityException(
- "Permission Denial: attempt to change stopped state from pid="
- + Binder.getCallingPid()
- + ", uid=" + callingUid + ", package=" + packageName);
- }
- enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
- true /* checkShell */, "stop package");
- // writer
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (!shouldFilterApplication(ps, callingUid, userId)
- && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) {
- scheduleWritePackageRestrictionsLocked(userId);
+ Pair<Boolean, String> wasNotLaunchedAndInstallerPackageName =
+ executeWithConsistentComputerReturningThrowing(computer -> {
+ if (computer.getInstantAppPackageName(callingUid) != null) {
+ return null;
}
+ final int permission = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+ final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+ if (!allowedByPermission
+ && !ArrayUtils.contains(computer.getPackagesForUid(callingUid), packageName)) {
+ throw new SecurityException(
+ "Permission Denial: attempt to change stopped state from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + callingUid + ", package=" + packageName);
+ }
+ computer.enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, true /* checkShell */, "stop package");
+
+ final PackageStateInternal packageState = computer.getPackageStateInternal(packageName);
+ final PackageUserState PackageUserState = packageState == null
+ ? null : packageState.getUserStateOrDefault(userId);
+ if (packageState == null
+ || computer.shouldFilterApplication(packageState, callingUid, userId)
+ || PackageUserState.isStopped() == stopped) {
+ return null;
+ }
+
+ return Pair.create(PackageUserState.isNotLaunched(),
+ packageState.getInstallSource().installerPackageName);
+ });
+ if (wasNotLaunchedAndInstallerPackageName != null) {
+ boolean wasNotLaunched = wasNotLaunchedAndInstallerPackageName.first;
+
+ commitPackageStateMutation(null, packageName, packageState -> {
+ PackageUserStateWrite userState = packageState.userState(userId);
+ userState.setStopped(stopped);
+ if (wasNotLaunched) {
+ userState.setNotLaunched(false);
+ }
+ });
+
+ scheduleWritePackageRestrictions(userId);
}
+
// If this would cause the app to leave force-stop, then also make sure to unhibernate the
// app if needed.
if (!stopped) {
@@ -6490,19 +6508,17 @@
}
void dumpSnapshotStats(PrintWriter pw, boolean isBrief) {
- if (!mSnapshotEnabled) {
- pw.println(" Snapshots disabled");
- } else {
- int hits = 0;
- int level = sSnapshotCorked.get();
- synchronized (mSnapshotLock) {
- if (mSnapshotComputer != null) {
- hits = mSnapshotComputer.getUsed();
- }
- }
- final long now = SystemClock.currentTimeMicro();
- mSnapshotStatistics.dump(pw, " ", now, hits, level, isBrief);
+ if (mSnapshotStatistics == null) {
+ return;
}
+ int hits = 0;
+ synchronized (mSnapshotLock) {
+ if (mSnapshotComputer != null) {
+ hits = mSnapshotComputer.getUsed();
+ }
+ }
+ final long now = SystemClock.currentTimeMicro();
+ mSnapshotStatistics.dump(pw, " ", now, hits, -1, isBrief);
}
/**
@@ -6641,8 +6657,9 @@
if (getInstallLocation() == loc) {
return true;
}
- if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL
- || loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+ if (loc == InstallLocationUtils.APP_INSTALL_AUTO
+ || loc == InstallLocationUtils.APP_INSTALL_INTERNAL
+ || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
android.provider.Settings.Global.putInt(mContext.getContentResolver(),
android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
return true;
@@ -6655,21 +6672,23 @@
// allow instant app access
return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
- PackageHelper.APP_INSTALL_AUTO);
+ InstallLocationUtils.APP_INSTALL_AUTO);
}
/** Called by UserManagerService */
void cleanUpUser(UserManagerService userManager, @UserIdInt int userId) {
synchronized (mLock) {
- mDirtyUsers.remove(userId);
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.remove(userId);
+ }
mUserNeedsBadging.delete(userId);
mPermissionManager.onUserRemoved(userId);
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
- mInstantAppRegistry.onUserRemovedLPw(userId);
mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
mAppsFilter.onUserDeleted(userId);
}
+ mInstantAppRegistry.onUserRemoved(userId);
}
/**
@@ -6688,7 +6707,7 @@
userTypeInstallablePackages, disallowedPackages);
}
synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
scheduleWritePackageListLocked(userId);
mAppsFilter.onUserCreated(userId);
}
@@ -6796,20 +6815,23 @@
return mComputer.isPackageSignedByKeySetExactly(packageName, ks);
}
- @GuardedBy("mLock")
- private void deletePackageIfUnusedLPr(final String packageName) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
+ private void deletePackageIfUnused(final String packageName) {
+ PackageStateInternal ps = getPackageStateInternal(packageName);
if (ps == null) {
return;
}
- if (!ps.isAnyInstalled(mUserManager.getUserIds())) {
- // TODO Implement atomic delete if package is unused
- // It is currently possible that the package will be deleted even if it is installed
- // after this method returns.
- mHandler.post(() -> mDeletePackageHelper.deletePackageX(
- packageName, PackageManager.VERSION_CODE_HIGHEST,
- 0, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/));
+ final SparseArray<? extends PackageUserStateInternal> userStates = ps.getUserStates();
+ for (int index = 0; index < userStates.size(); index++) {
+ if (userStates.valueAt(index).isInstalled()) {
+ return;
+ }
}
+ // TODO Implement atomic delete if package is unused
+ // It is currently possible that the package will be deleted even if it is installed
+ // after this method returns.
+ mHandler.post(() -> mDeletePackageHelper.deletePackageX(
+ packageName, PackageManager.VERSION_CODE_HIGHEST,
+ 0, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/));
}
private AndroidPackage getPackage(String packageName) {
@@ -6985,7 +7007,7 @@
@Override
public PackageList getPackageList(@Nullable PackageListObserver observer) {
final ArrayList<String> list = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
AndroidPackage pkg = packageState.getPkg();
if (pkg != null) {
list.add(pkg.getPackageName());
@@ -6993,18 +7015,14 @@
});
final PackageList packageList = new PackageList(list, observer);
if (observer != null) {
- synchronized (mLock) {
- mPackageListObservers.add(packageList);
- }
+ mPackageObserverHelper.addObserver(packageList);
}
return packageList;
}
@Override
public void removePackageListObserver(PackageListObserver observer) {
- synchronized (mLock) {
- mPackageListObservers.remove(observer);
- }
+ mPackageObserverHelper.removeObserver(observer);
}
@Override
@@ -7203,6 +7221,16 @@
SparseArray<String> profileOwnerPackages) {
mProtectedPackages.setDeviceAndProfileOwnerPackages(
deviceOwnerUserId, deviceOwnerPackage, profileOwnerPackages);
+ final ArraySet<Integer> usersWithPoOrDo = new ArraySet<>();
+ if (deviceOwnerPackage != null) {
+ usersWithPoOrDo.add(deviceOwnerUserId);
+ }
+ final int sz = profileOwnerPackages.size();
+ for (int i = 0; i < sz; i++) {
+ if (profileOwnerPackages.valueAt(i) != null) {
+ removeAllNonSystemPackageSuspensions(profileOwnerPackages.keyAt(i));
+ }
+ }
}
@Override
@@ -7276,32 +7304,32 @@
@Override
public void grantImplicitAccess(int userId, Intent intent,
int recipientAppId, int visibleUid, boolean direct, boolean retainOnUpdate) {
- synchronized (mLock) {
- final AndroidPackage visiblePackage = getPackage(visibleUid);
+ boolean accessGranted = executeWithConsistentComputerReturning(computer -> {
+ final AndroidPackage visiblePackage = computer.getPackage(visibleUid);
final int recipientUid = UserHandle.getUid(userId, recipientAppId);
- if (visiblePackage == null || getPackage(recipientUid) == null) {
- return;
+ if (visiblePackage == null || computer.getPackage(recipientUid) == null) {
+ return false;
}
- final boolean instantApp =
- isInstantAppInternal(visiblePackage.getPackageName(), userId, visibleUid);
- final boolean accessGranted;
+ final boolean instantApp = computer.isInstantAppInternal(
+ visiblePackage.getPackageName(), userId, visibleUid);
if (instantApp) {
if (!direct) {
// if the interaction that lead to this granting access to an instant app
// was indirect (i.e.: URI permission grant), do not actually execute the
// grant.
- return;
+ return false;
}
- accessGranted = mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+ return mInstantAppRegistry.grantInstantAccess(userId, intent,
recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
} else {
- accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
+ return mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
retainOnUpdate);
}
- if (accessGranted) {
- ApplicationPackageManager.invalidateGetPackagesForUidCache();
- }
+ });
+
+ if (accessGranted) {
+ ApplicationPackageManager.invalidateGetPackagesForUidCache();
}
}
@@ -7314,7 +7342,8 @@
@Override
public void pruneInstantApps() {
- mInstantAppRegistry.pruneInstantApps();
+ executeWithConsistentComputer(computer ->
+ mInstantAppRegistry.pruneInstantApps(computer));
}
@Override
@@ -7361,7 +7390,7 @@
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
final ArrayList<PackageInfo> overlayPackages = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
final AndroidPackage pkg = packageState.getPkg();
if (pkg != null && pkg.getOverlayTarget() != null) {
PackageInfo pkgInfo = generatePackageInfo(packageState, 0, userId);
@@ -7377,7 +7406,7 @@
@Override
public List<String> getTargetPackageNames(int userId) {
List<String> targetPackages = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
final AndroidPackage pkg = packageState.getPkg();
if (pkg != null && !pkg.isOverlay()) {
targetPackages.add(pkg.getPackageName());
@@ -7390,53 +7419,8 @@
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
@Nullable OverlayPaths overlayPaths,
@NonNull Set<String> outUpdatedPackageNames) {
- boolean modified = false;
- synchronized (mLock) {
- final AndroidPackage targetPkg = mPackages.get(targetPackageName);
- if (targetPackageName == null || targetPkg == null) {
- Slog.e(TAG, "failed to find package " + targetPackageName);
- return false;
- }
-
- if (targetPkg.getLibraryNames() != null) {
- // Set the overlay paths for dependencies of the shared library.
- for (final String libName : targetPkg.getLibraryNames()) {
- final SharedLibraryInfo info = getSharedLibraryInfo(libName,
- SharedLibraryInfo.VERSION_UNDEFINED);
- if (info == null) {
- continue;
- }
- final List<VersionedPackage> dependents = getPackagesUsingSharedLibrary(
- info, 0, Process.SYSTEM_UID, userId);
- if (dependents == null) {
- continue;
- }
- for (final VersionedPackage dependent : dependents) {
- final PackageSetting ps = mSettings.getPackageLPr(
- dependent.getPackageName());
- if (ps == null) {
- continue;
- }
- if (ps.setOverlayPathsForLibrary(libName, overlayPaths, userId)) {
- outUpdatedPackageNames.add(dependent.getPackageName());
- modified = true;
- }
- }
- }
- }
-
- final PackageSetting ps = mSettings.getPackageLPr(targetPackageName);
- if (ps.setOverlayPaths(overlayPaths, userId)) {
- outUpdatedPackageNames.add(targetPackageName);
- modified = true;
- }
-
- if (modified) {
- invalidatePackageInfoCache();
- }
- }
-
- return true;
+ return PackageManagerService.this.setEnabledOverlayPackages(userId, targetPackageName,
+ overlayPaths, outUpdatedPackageNames);
}
@Override
@@ -7504,15 +7488,13 @@
@Override
public boolean hasInstantApplicationMetadata(String packageName, int userId) {
- synchronized (mLock) {
- return mInstantAppRegistry.hasInstantApplicationMetadataLPr(packageName, userId);
- }
+ return mInstantAppRegistry.hasInstantApplicationMetadata(packageName, userId);
}
@Override
public void notifyPackageUse(String packageName, int reason) {
synchronized (mLock) {
- PackageManagerService.this.notifyPackageUseLocked(packageName, reason);
+ PackageManagerService.this.notifyPackageUseInternal(packageName, reason);
}
}
@@ -7549,30 +7531,24 @@
}
@Override
- public void forEachPackage(Consumer<AndroidPackage> actionLocked) {
- PackageManagerService.this.forEachPackage(actionLocked);
- }
-
- @Override
public void forEachPackageSetting(Consumer<PackageSetting> actionLocked) {
PackageManagerService.this.forEachPackageSetting(actionLocked);
}
@Override
- public void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
- PackageManagerService.this.forEachPackageState(locked, action);
+ public void forEachPackageState(Consumer<PackageStateInternal> action) {
+ PackageManagerService.this.forEachPackageState(action);
}
@Override
- public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked,
+ public void forEachPackage(Consumer<AndroidPackage> action) {
+ PackageManagerService.this.forEachPackage(action);
+ }
+
+ @Override
+ public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> action,
@UserIdInt int userId) {
- forEachInstalledPackage(true, actionLocked, userId);
- }
-
- @Override
- public void forEachInstalledPackage(boolean locked,
- @NonNull Consumer<AndroidPackage> action, int userId) {
- PackageManagerService.this.forEachInstalledPackage(locked, action, userId);
+ PackageManagerService.this.forEachInstalledPackage(action, userId);
}
@Override
@@ -7695,9 +7671,7 @@
@Override
public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
- synchronized (mLock) {
- mSettings.updateRuntimePermissionsFingerprintLPr(userId);
- }
+ mSettings.updateRuntimePermissionsFingerprint(userId);
}
@Override
@@ -7736,9 +7710,7 @@
@Override
public boolean isPermissionUpgradeNeeded(int userId) {
- synchronized (mLock) {
- return mSettings.isPermissionUpgradeNeededLPr(userId);
- }
+ return mSettings.isPermissionUpgradeNeeded(userId);
}
@Override
@@ -7910,14 +7882,104 @@
}
}
+ private boolean setEnabledOverlayPackages(@UserIdInt int userId,
+ @NonNull String targetPackageName, @Nullable OverlayPaths newOverlayPaths,
+ @NonNull Set<String> outUpdatedPackageNames) {
+ synchronized (mOverlayPathsLock) {
+ final ArrayMap<String, ArraySet<String>> libNameToModifiedDependents = new ArrayMap<>();
+ Boolean targetModified = executeWithConsistentComputerReturning(computer -> {
+ final PackageStateInternal packageState = computer.getPackageStateInternal(
+ targetPackageName);
+ final AndroidPackage targetPkg =
+ packageState == null ? null : packageState.getPkg();
+ if (targetPackageName == null || targetPkg == null) {
+ Slog.e(TAG, "failed to find package " + targetPackageName);
+ return null;
+ }
+
+ if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+ newOverlayPaths)) {
+ return false;
+ }
+
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ for (final String libName : targetPkg.getLibraryNames()) {
+ ArraySet<String> modifiedDependents = null;
+
+ final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = computer
+ .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (final VersionedPackage dependent : dependents) {
+ final PackageStateInternal dependentState =
+ computer.getPackageStateInternal(dependent.getPackageName());
+ if (dependentState == null) {
+ continue;
+ }
+ if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+ .getSharedLibraryOverlayPaths()
+ .get(libName), newOverlayPaths)) {
+ String dependentPackageName = dependent.getPackageName();
+ modifiedDependents = ArrayUtils.add(modifiedDependents,
+ dependentPackageName);
+ outUpdatedPackageNames.add(dependentPackageName);
+ }
+ }
+
+ if (modifiedDependents != null) {
+ libNameToModifiedDependents.put(libName, modifiedDependents);
+ }
+ }
+ }
+
+ outUpdatedPackageNames.add(targetPackageName);
+ return true;
+ });
+
+ if (targetModified == null) {
+ // Null indicates error
+ return false;
+ } else if (!targetModified) {
+ // Treat non-modification as a successful commit
+ return true;
+ }
+
+ commitPackageStateMutation(null, mutator -> {
+ mutator.forPackage(targetPackageName)
+ .userState(userId)
+ .setOverlayPaths(newOverlayPaths);
+
+ for (int mapIndex = 0; mapIndex < libNameToModifiedDependents.size(); mapIndex++) {
+ String libName = libNameToModifiedDependents.keyAt(mapIndex);
+ ArraySet<String> modifiedDependents =
+ libNameToModifiedDependents.valueAt(mapIndex);
+ for (int setIndex = 0; setIndex < modifiedDependents.size(); setIndex++) {
+ mutator.forPackage(modifiedDependents.valueAt(setIndex))
+ .userState(userId)
+ .setOverlayPathsForLibrary(libName, newOverlayPaths);
+ }
+ }
+ });
+ }
+
+ invalidatePackageInfoCache();
+
+ return true;
+ }
+
@Override
public int getRuntimePermissionsVersion(@UserIdInt int userId) {
Preconditions.checkArgumentNonnegative(userId);
enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
"getRuntimePermissionVersion");
- synchronized (mLock) {
- return mSettings.getDefaultRuntimePermissionsVersionLPr(userId);
- }
+ return mSettings.getDefaultRuntimePermissionsVersion(userId);
}
@Override
@@ -7926,9 +7988,7 @@
Preconditions.checkArgumentNonnegative(userId);
enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
"setRuntimePermissionVersion");
- synchronized (mLock) {
- mSettings.setDefaultRuntimePermissionsVersionLPr(version, userId);
- }
+ mSettings.setDefaultRuntimePermissionsVersion(version, userId);
}
private void enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
@@ -7964,58 +8024,19 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
@Nullable
PackageStateInternal getPackageStateInternal(String packageName) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) computer.getPackageStateInternal(packageName);
- if (pkgSetting == null) {
- return null;
- }
-
- return new PackageSetting(pkgSetting);
- }
- } else {
- return computer.getPackageStateInternal(packageName);
- }
+ return mComputer.getPackageStateInternal(packageName);
}
@Nullable
PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) computer.getPackageStateInternal(packageName, callingUid);
- if (pkgSetting == null) {
- return null;
- }
-
- return new PackageSetting(pkgSetting);
- }
- } else {
- return computer.getPackageStateInternal(packageName, callingUid);
- }
+ return mComputer.getPackageStateInternal(packageName, callingUid);
}
@Nullable
PackageStateInternal getPackageStateInstalledFiltered(@NonNull String packageName,
int callingUid, @UserIdInt int userId) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) filterPackageStateForInstalledAndFiltered(computer,
- packageName, callingUid, userId);
- if (pkgSetting == null) {
- return null;
- }
- return new PackageSetting(pkgSetting);
- }
- } else {
- return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid,
- userId);
- }
+ return filterPackageStateForInstalledAndFiltered(mComputer, packageName, callingUid,
+ userId);
}
@Nullable
@@ -8057,31 +8078,19 @@
}
}
- void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
- if (locked) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(mComputer.getPackageStates(), action);
- }
- } else {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(computer.getPackageStates(), action);
- }
- } else {
- forEachPackageState(computer.getPackageStates(), action);
- }
- }
+ void forEachPackageState(Consumer<PackageStateInternal> consumer) {
+ forEachPackageState(mComputer.getPackageStates(), consumer);
}
- void forEachPackage(Consumer<AndroidPackage> action) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackage(computer.getPackageStates(), action);
+ void forEachPackage(Consumer<AndroidPackage> consumer) {
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ mComputer.getPackageStates();
+ int size = packageStates.size();
+ for (int index = 0; index < size; index++) {
+ PackageStateInternal packageState = packageStates.valueAt(index);
+ if (packageState.getPkg() != null) {
+ consumer.accept(packageState.getPkg());
}
- } else {
- forEachPackage(computer.getPackageStates(), action);
}
}
@@ -8095,19 +8104,7 @@
}
}
- private void forEachPackage(
- @NonNull ArrayMap<String, ? extends PackageStateInternal> packageStates,
- @NonNull Consumer<AndroidPackage> consumer) {
- int size = packageStates.size();
- for (int index = 0; index < size; index++) {
- PackageStateInternal packageState = packageStates.valueAt(index);
- if (packageState.getPkg() != null) {
- consumer.accept(packageState.getPkg());
- }
- }
- }
-
- void forEachInstalledPackage(boolean locked, @NonNull Consumer<AndroidPackage> action,
+ void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> action,
@UserIdInt int userId) {
Consumer<PackageStateInternal> actionWrapped = packageState -> {
if (packageState.getPkg() != null
@@ -8115,84 +8112,37 @@
action.accept(packageState.getPkg());
}
};
- if (locked) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(mComputer.getPackageStates(), actionWrapped);
- }
- } else {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(computer.getPackageStates(), actionWrapped);
- }
- } else {
- forEachPackageState(computer.getPackageStates(), actionWrapped);
- }
- }
+ forEachPackageState(mComputer.getPackageStates(), actionWrapped);
}
- private void executeWithConsistentComputer(
+ // TODO: Make private
+ void executeWithConsistentComputer(
@NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <T> T executeWithConsistentComputerReturning(
@NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- return function.apply(computer);
- }
- } else {
- return function.apply(computer);
- }
+ return function.apply(snapshotComputer());
}
private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing(
@NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer)
throws ExceptionType {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
executeWithConsistentComputerThrowing2(
@NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing(
@NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function)
throws ExceptionType {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- return function.apply(computer);
- }
- } else {
- return function.apply(computer);
- }
+ return function.apply(snapshotComputer());
}
boolean isHistoricalPackageUsageAvailable() {
@@ -8276,9 +8226,7 @@
if (!isInstantApp(packageName, userId)) {
return null;
}
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId);
- }
+ return mInstantAppRegistry.getInstantAppAndroidId(packageName, userId);
}
@Override
@@ -8333,10 +8281,13 @@
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
- synchronized (mLock) {
- mSettings.setHarmfulAppWarningLPw(packageName, warning, userId);
- scheduleWritePackageRestrictionsLocked(userId);
+ PackageStateMutator.Result result = commitPackageStateMutation(null, packageName,
+ packageState -> packageState.userState(userId)
+ .setHarmfulAppWarning(warning == null ? null : warning.toString()));
+ if (result.isSpecificPackageNull()) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
+ scheduleWritePackageRestrictions(userId);
}
@Nullable
@@ -8388,14 +8339,23 @@
@Override
public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) {
enforceOwnerRights(packageName, Binder.getCallingUid());
- final boolean changed;
- synchronized (mLock) {
- changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup,
- new ArraySet<>(mimeTypes));
+ mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup);
+ if (existingMimeTypes == null) {
+ throw new IllegalArgumentException("Unknown MIME group " + mimeGroup
+ + " for package " + packageName);
}
- if (changed) {
- applyMimeGroupChanges(packageName, mimeGroup);
+ if (existingMimeTypes.size() == mimeTypes.size()
+ && existingMimeTypes.containsAll(mimeTypes)) {
+ return;
}
+
+ ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes);
+ commitPackageStateMutation(null, packageName, packageStateWrite -> {
+ packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet);
+ });
+ applyMimeGroupChanges(packageName, mimeGroup);
}
@Override
@@ -8426,8 +8386,15 @@
enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "setSplashScreenTheme");
enforceOwnerRights(packageName, callingUid);
- mutateInstalledPackageSetting(packageName, callingUid, userId,
- pkgSetting -> pkgSetting.setSplashScreenTheme(userId, themeId));
+
+ PackageStateInternal packageState = getPackageStateInstalledFiltered(packageName,
+ callingUid, userId);
+ if (packageState == null) {
+ return;
+ }
+
+ commitPackageStateMutation(null, packageName, state ->
+ state.userState(userId).setSplashScreenTheme(themeId));
}
@Override
@@ -8442,6 +8409,7 @@
* Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's
* writeLegacyPermissionsTEMP() beforehand.
*
+ * TODO: In the meantime, can this be moved to a schedule call?
* TODO(b/182523293): This should be removed once we finish migration of permission storage.
*/
void writeSettingsLPrTEMP() {
@@ -8532,57 +8500,54 @@
}
final int[] allUsers = mInjector.getUserManagerService().getUserIds();
+ final List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size());
+ for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) {
+ final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i);
+ final PackageStateInternal ps = getPackageStateInternal(perPackage.packageName);
+ if (ps == null) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package not found = "
+ + perPackage.packageName);
+ }
+ continue;
+ }
+ if (ps.getAppId() < Process.FIRST_APPLICATION_UID) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package is system, appId="
+ + ps.getAppId());
+ }
+ continue;
+ }
- List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size());
- synchronized (mLock) {
- for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) {
- final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i);
- final PackageSetting ps = mSettings.mPackages.get(perPackage.packageName);
- if (ps == null) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: package not found = "
- + perPackage.packageName);
- }
+ final AndroidPackage pkg = ps.getPkg();
+ if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
+ || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ if (perPackage.sha256certificate != null
+ && !pkg.getSigningDetails().hasSha256Certificate(
+ perPackage.sha256certificate)) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ for (int userId : allUsers) {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
continue;
}
- if (ps.getAppId() < Process.FIRST_APPLICATION_UID) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: package is system, appId="
- + ps.getAppId());
- }
- continue;
- }
-
- final AndroidPackage pkg = ps.getPkg();
- if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
- || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = "
- + perPackage.packageName + ":" + pkg.getLongVersionCode());
- }
- continue;
- }
- if (perPackage.sha256certificate != null
- && !pkg.getSigningDetails().hasSha256Certificate(
- perPackage.sha256certificate)) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = "
- + perPackage.packageName + ":" + pkg.getLongVersionCode());
- }
- continue;
- }
- for (int userId : allUsers) {
- if (!ps.getInstalled(userId)) {
- continue;
- }
- final int uid = UserHandle.getUid(userId, ps.getAppId());
- final PerUidReadTimeouts perUid = new PerUidReadTimeouts();
- perUid.uid = uid;
- perUid.minTimeUs = perPackage.timeouts.minTimeUs;
- perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs;
- perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs;
- result.add(perUid);
- }
+ final int uid = UserHandle.getUid(userId, ps.getAppId());
+ final PerUidReadTimeouts perUid = new PerUidReadTimeouts();
+ perUid.uid = uid;
+ perUid.minTimeUs = perPackage.timeouts.minTimeUs;
+ perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs;
+ perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs;
+ result.add(perUid);
}
}
return result.toArray(new PerUidReadTimeouts[result.size()]);
@@ -8600,33 +8565,23 @@
private void setKeepUninstalledPackagesInternal(List<String> packageList) {
Preconditions.checkNotNull(packageList);
- List<String> removedFromList = null;
- synchronized (mLock) {
- if (mKeepUninstalledPackages != null) {
- final int packagesCount = mKeepUninstalledPackages.size();
- for (int i = 0; i < packagesCount; i++) {
- String oldPackage = mKeepUninstalledPackages.get(i);
- if (packageList != null && packageList.contains(oldPackage)) {
- continue;
- }
- if (removedFromList == null) {
- removedFromList = new ArrayList<>();
- }
- removedFromList.add(oldPackage);
- }
- }
- mKeepUninstalledPackages = new ArrayList<>(packageList);
- if (removedFromList != null) {
- final int removedCount = removedFromList.size();
- for (int i = 0; i < removedCount; i++) {
- deletePackageIfUnusedLPr(removedFromList.get(i));
- }
+ synchronized (mKeepUninstalledPackages) {
+ List<String> toRemove = new ArrayList<>(mKeepUninstalledPackages);
+ toRemove.removeAll(packageList); // Do not remove anything still in the list
+
+ mKeepUninstalledPackages.clear();
+ mKeepUninstalledPackages.addAll(packageList);
+
+ for (int i = 0; i < toRemove.size(); i++) {
+ deletePackageIfUnused(toRemove.get(i));
}
}
}
boolean shouldKeepUninstalledPackageLPr(String packageName) {
- return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+ synchronized (mKeepUninstalledPackages) {
+ return mKeepUninstalledPackages.contains(packageName);
+ }
}
@Override
@@ -8904,7 +8859,7 @@
*/
@NonNull
public PackageStateMutator.InitialState recordInitialState() {
- return mPackageStateMutator.initialState(mChangedPackagesSequenceNumber);
+ return mPackageStateMutator.initialState(mChangedPackagesTracker.getSequenceNumber());
}
/**
@@ -8917,7 +8872,7 @@
@NonNull Consumer<PackageStateMutator> consumer) {
synchronized (mPackageStateWriteLock) {
final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
- initialState, mChangedPackagesSequenceNumber);
+ initialState, mChangedPackagesTracker.getSequenceNumber());
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
@@ -8939,7 +8894,7 @@
@NonNull Consumer<PackageStateWrite> consumer) {
synchronized (mPackageStateWriteLock) {
final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
- initialState, mChangedPackagesSequenceNumber);
+ initialState, mChangedPackagesTracker.getSequenceNumber());
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
@@ -8951,9 +8906,14 @@
consumer.accept(state);
}
- onChanged();
+ state.onChanged();
}
return PackageStateMutator.Result.SUCCESS;
}
+
+ void notifyInstantAppPackageInstalled(String packageName, int[] newUsers) {
+ executeWithConsistentComputer(computer ->
+ mInstantAppRegistry.onPackageInstalled(computer, packageName, newUsers));
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index db60686..d12c826 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -57,6 +57,7 @@
public IncrementalManager incrementalManager;
public PackageInstallerService installerService;
public InstantAppRegistry instantAppRegistry;
+ public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
public InstantAppResolverConnection instantAppResolverConnection;
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
@@ -88,6 +89,7 @@
public @Nullable String defaultTextClassifierPackage;
public @Nullable String systemTextClassifierPackage;
public @Nullable String overlayConfigSignaturePackage;
+ public @NonNull String requiredSupplementalProcessPackage;
public ViewCompiler viewCompiler;
public @Nullable String retailDemoPackage;
public @Nullable String recentsPackage;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 1d2b829..d6340b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -43,6 +43,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.ResolveInfo;
@@ -79,8 +80,8 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -463,18 +464,18 @@
* <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
* and is not tamperproof.
*/
- private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
- PackageSetting disabledPkgSetting) {
- if (pkgSetting.getSigningDetails().checkCapability(
+ private static boolean matchSignatureInSystem(@NonNull String packageName,
+ @NonNull SigningDetails signingDetails, PackageSetting disabledPkgSetting) {
+ if (signingDetails.checkCapability(
disabledPkgSetting.getSigningDetails(),
SigningDetails.CertCapabilities.INSTALLED_DATA)
|| disabledPkgSetting.getSigningDetails().checkCapability(
- pkgSetting.getSigningDetails(),
+ signingDetails,
SigningDetails.CertCapabilities.ROLLBACK)) {
return true;
} else {
logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
- pkgSetting.getPackageName());
+ packageName);
return false;
}
}
@@ -482,12 +483,6 @@
/** Default is to not use fs-verity since it depends on kernel support. */
private static final int FSVERITY_DISABLED = 0;
- /**
- * Experimental implementation targeting priv apps, with Android specific kernel patches to
- * extend fs-verity.
- */
- private static final int FSVERITY_LEGACY = 1;
-
/** Standard fs-verity. */
private static final int FSVERITY_ENABLED = 2;
@@ -498,10 +493,6 @@
== FSVERITY_ENABLED;
}
- static boolean isLegacyApkVerityEnabled() {
- return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
- }
-
/** Returns true to force apk verification if the package is considered privileged. */
static boolean isApkVerificationForced(@Nullable PackageSetting ps) {
// TODO(b/154310064): re-enable.
@@ -546,7 +537,8 @@
}
if (!match && isApkVerificationForced(disabledPkgSetting)) {
- match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
+ match = matchSignatureInSystem(packageName, pkgSetting.getSigningDetails(),
+ disabledPkgSetting);
}
if (!match && isRollback) {
@@ -814,27 +806,37 @@
final PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null || pkg == null) {
Slog.i(TAG, "Invalid package file " + packagePath);
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
return ret;
}
final File packageFile = new File(packagePath);
final long sizeBytes;
try {
- sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
+ sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
} catch (IOException e) {
if (!packageFile.exists()) {
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
} else {
- ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+ ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
}
return ret;
}
- final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
- pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
-
+ final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_INVALID);
+ sessionParams.appPackageName = pkg.getPackageName();
+ sessionParams.installLocation = pkg.getInstallLocation();
+ sessionParams.sizeBytes = sizeBytes;
+ sessionParams.installFlags = flags;
+ final int recommendedInstallLocation;
+ try {
+ recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(context,
+ sessionParams);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
ret.packageName = pkg.getPackageName();
ret.splitNames = pkg.getSplitNames();
ret.versionCode = pkg.getVersionCode();
@@ -846,7 +848,6 @@
ret.recommendedInstallLocation = recommendedInstallLocation;
ret.multiArch = pkg.isMultiArch();
ret.debuggable = pkg.isDebuggable();
-
return ret;
}
@@ -866,7 +867,7 @@
throw new PackageManagerException(result.getErrorCode(),
result.getErrorMessage(), result.getException());
}
- return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+ return InstallLocationUtils.calculateInstalledSize(result.getResult(), abiOverride);
} catch (PackageManagerException | IOException e) {
Slog.w(TAG, "Failed to calculate installed size: " + e);
return -1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 99f70b2..d4fcd06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -98,7 +98,7 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -593,7 +593,7 @@
null /* splitApkPaths */, null /* splitRevisionCodes */,
apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
null /* splitTypes */);
- sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
+ sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
params.sessionParams.abiOverride, fd.getFileDescriptor());
} catch (IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
@@ -922,7 +922,7 @@
final List<SharedLibraryInfo> libs = libsSlice.getList();
for (int l = 0, lsize = libs.size(); l < lsize; ++l) {
SharedLibraryInfo lib = libs.get(l);
- if (lib.getType() == SharedLibraryInfo.TYPE_SDK) {
+ if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
name = lib.getName() + ":" + lib.getLongVersion();
break;
}
@@ -1649,11 +1649,11 @@
private int runGetInstallLocation() throws RemoteException {
int loc = mInterface.getInstallLocation();
String locStr = "invalid";
- if (loc == PackageHelper.APP_INSTALL_AUTO) {
+ if (loc == InstallLocationUtils.APP_INSTALL_AUTO) {
locStr = "auto";
- } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+ } else if (loc == InstallLocationUtils.APP_INSTALL_INTERNAL) {
locStr = "internal";
- } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+ } else if (loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
locStr = "external";
}
getOutPrintWriter().println(loc + "[" + locStr + "]");
diff --git a/services/core/java/com/android/server/pm/PackageObserverHelper.java b/services/core/java/com/android/server/pm/PackageObserverHelper.java
new file mode 100644
index 0000000..ec42f2e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageObserverHelper.java
@@ -0,0 +1,86 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+class PackageObserverHelper {
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ // True set of observers, immutable, used to iterate without blocking the lock, since
+ // callbacks can take a long time to return. The previous alternative used a bunch of
+ // list copies on each notify call, which is suboptimal in cases of few mutations and
+ // lots of notifications.
+ @NonNull
+ @GuardedBy("mLock")
+ private ArraySet<PackageListObserver> mActiveSnapshot = new ArraySet<>();
+
+ public void addObserver(@NonNull PackageListObserver observer) {
+ synchronized (mLock) {
+ ArraySet<PackageListObserver> set = new ArraySet<>(mActiveSnapshot);
+ set.add(observer);
+ mActiveSnapshot = set;
+ }
+ }
+
+ public void removeObserver(@NonNull PackageListObserver observer) {
+ synchronized (mLock) {
+ ArraySet<PackageListObserver> set = new ArraySet<>(mActiveSnapshot);
+ set.remove(observer);
+ mActiveSnapshot = set;
+ }
+ }
+
+ public void notifyAdded(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageAdded(packageName, uid);
+ }
+ }
+
+ public void notifyChanged(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageChanged(packageName, uid);
+ }
+ }
+
+ public void notifyRemoved(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageRemoved(packageName, uid);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 9bfb7d1..9db215e 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -28,7 +28,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.rollback.RollbackInfo;
@@ -43,11 +42,12 @@
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.rollback.RollbackManagerInternal;
import java.io.File;
@@ -291,8 +291,8 @@
throws PackageManagerException {
// Before marking the session as ready, start checkpoint service if available
try {
- if (PackageHelper.getStorageManager().supportsCheckpoint()) {
- PackageHelper.getStorageManager().startCheckpoint(2);
+ if (InstallLocationUtils.getStorageManager().supportsCheckpoint()) {
+ InstallLocationUtils.getStorageManager().startCheckpoint(2);
}
} catch (Exception e) {
// Failed to get hold of StorageManager
@@ -544,7 +544,7 @@
*/
private void checkActiveSessions() throws PackageManagerException {
try {
- checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
+ checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint());
} catch (RemoteException e) {
throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
"Can't query fs-checkpoint status : " + e);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 5fc840c..d2abc69 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -68,10 +68,10 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.function.Predicate;
/**
* Settings data for a particular package we know about.
+ *
* @hide
*/
@DataClass(genGetters = true, genConstructor = false, genSetters = false, genBuilder = false)
@@ -189,7 +189,7 @@
private boolean forceQueryableOverride;
@NonNull
- private PackageStateUnserialized pkgState = new PackageStateUnserialized();
+ private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
@NonNull
private UUID mDomainSetId;
@@ -722,10 +722,6 @@
return readUserState(userId).getEnabledState();
}
- String getLastDisabledAppCaller(int userId) {
- return readUserState(userId).getLastDisableAppCaller();
- }
-
void setInstalled(boolean inst, int userId) {
modifyUserState(userId).setInstalled(inst);
onChanged();
@@ -753,14 +749,6 @@
onChanged();
}
- boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
- boolean changed = modifyUserState(userId).setOverlayPaths(overlayPaths);
- if (changed) {
- onChanged();
- }
- return changed;
- }
-
@NonNull
OverlayPaths getOverlayPaths(int userId) {
return readUserState(userId).getOverlayPaths();
@@ -773,11 +761,6 @@
return changed;
}
- @NonNull
- Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) {
- return readUserState(userId).getSharedLibraryOverlayPaths();
- }
-
boolean isAnyInstalled(int[] users) {
for (int user: users) {
if (readUserState(user).isInstalled()) {
@@ -850,55 +833,6 @@
onChanged();
}
- boolean getSuspended(int userId) {
- return readUserState(userId).isSuspended();
- }
-
- boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
- PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- final SuspendParams newSuspendParams = SuspendParams.getInstanceOrNull(dialogInfo,
- appExtras, launcherExtras);
- if (existingUserState.getSuspendParams() == null) {
- existingUserState.setSuspendParams(new ArrayMap<>());
- }
- final SuspendParams oldSuspendParams =
- existingUserState.getSuspendParams().put(suspendingPackage, newSuspendParams);
- onChanged();
- return !Objects.equals(oldSuspendParams, newSuspendParams);
- }
-
- boolean removeSuspension(String suspendingPackage, int userId) {
- boolean wasModified = false;
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- if (existingUserState.getSuspendParams() != null) {
- if (existingUserState.getSuspendParams().remove(suspendingPackage) != null) {
- wasModified = true;
- }
- if (existingUserState.getSuspendParams().size() == 0) {
- existingUserState.setSuspendParams(null);
- }
- }
- onChanged();
- return wasModified;
- }
-
- void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- if (existingUserState.getSuspendParams() != null) {
- for (int i = existingUserState.getSuspendParams().size() - 1; i >= 0; i--) {
- final String suspendingPackage = existingUserState.getSuspendParams().keyAt(i);
- if (suspendingPackagePredicate.test(suspendingPackage)) {
- existingUserState.getSuspendParams().removeAt(i);
- }
- }
- if (existingUserState.getSuspendParams().size() == 0) {
- existingUserState.setSuspendParams(null);
- }
- }
- onChanged();
- }
-
public boolean getInstantApp(int userId) {
return readUserState(userId).isInstantApp();
}
@@ -1095,8 +1029,8 @@
}
/**
- * TODO (b/170263003) refactor to dump to permissiongr proto
- * Dumps the permissions that are granted to users for this package.
+ * TODO (b/170263003) refactor to dump to permissiongr proto Dumps the permissions that are
+ * granted to users for this package.
*/
void writePackageUserPermissionsProto(ProtoOutputStream proto, long fieldId,
List<UserInfo> users, LegacyPermissionDataProvider dataProvider) {
@@ -1154,16 +1088,6 @@
}
}
- void setHarmfulAppWarning(int userId, String harmfulAppWarning) {
- modifyUserState(userId).setHarmfulAppWarning(harmfulAppWarning);
- onChanged();
- }
-
- String getHarmfulAppWarning(int userId) {
- PackageUserState userState = readUserState(userId);
- return userState.getHarmfulAppWarning();
- }
-
/**
* @see #mPath
*/
@@ -1175,9 +1099,8 @@
}
/**
- * @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
- *
* @param userId the specific user to change the label/icon for
+ * @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
@@ -1188,9 +1111,8 @@
}
/**
- * @see PackageUserStateImpl#resetOverrideComponentLabelIcon()
- *
* @param userId the specific user to reset
+ * @see PackageUserStateImpl#resetOverrideComponentLabelIcon()
*/
public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
modifyUserState(userId).resetOverrideComponentLabelIcon();
@@ -1198,19 +1120,9 @@
}
/**
- * @param userId the specified user to modify the theme for
- * @param themeName the theme name to persist
- * @see android.window.SplashScreen#setSplashScreenTheme(int)
- */
- public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
- modifyUserState(userId).setSplashScreenTheme(themeName);
- onChanged();
- }
-
- /**
* @param userId the specified user to get the theme setting from
- * @return the theme name previously persisted for the user or null
- * if no splashscreen theme is persisted.
+ * @return the theme name previously persisted for the user or null if no splashscreen theme is
+ * persisted.
* @see android.window.SplashScreen#setSplashScreenTheme(int)
*/
@Nullable
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 802f701..bb82e6a 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -102,9 +102,7 @@
if (DEBUG_PREFERRED) {
Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
}
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
Slog.v(TAG, "No preferred activity to return");
@@ -121,9 +119,7 @@
if (changedUsers.size() > 0) {
updateDefaultHomeNotLocked(changedUsers);
mPm.postPreferredActivityChangedBroadcast(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -214,7 +210,7 @@
Settings.removeFilters(pir, filter, existing);
}
pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
- mPm.scheduleWritePackageRestrictionsLocked(userId);
+ mPm.scheduleWritePackageRestrictions(userId);
}
if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
mPm.postPreferredActivityChangedBroadcast(userId);
@@ -399,7 +395,7 @@
synchronized (mPm.mLock) {
mPm.mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
new PersistentPreferredActivity(filter, activity, true));
- mPm.scheduleWritePackageRestrictionsLocked(userId);
+ mPm.scheduleWritePackageRestrictions(userId);
}
if (isHomeFilter(filter)) {
updateDefaultHomeNotLocked(userId);
@@ -420,9 +416,7 @@
if (changed) {
updateDefaultHomeNotLocked(userId);
mPm.postPreferredActivityChangedBroadcast(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -603,9 +597,7 @@
}
updateDefaultHomeNotLocked(userId);
resetNetworkPolicies(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 19f180f..c0e191f 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -29,6 +29,7 @@
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.pkg.SharedUserApi;
import libcore.io.IoUtils;
@@ -346,7 +347,7 @@
}
private static int getTargetSdkVersionForSeInfo(AndroidPackage pkg,
- SharedUserSetting sharedUserSetting, PlatformCompat compatibility) {
+ SharedUserApi sharedUser, PlatformCompat compatibility) {
// Apps which share a sharedUserId must be placed in the same selinux domain. If this
// package is the first app installed as this shared user, set seInfoTargetSdkVersion to its
// targetSdkVersion. These are later adjusted in PackageManagerService's constructor to be
@@ -355,8 +356,8 @@
// NOTE: As new packages are installed / updated, the shared user's seinfoTargetSdkVersion
// will NOT be modified until next boot, even if a lower targetSdkVersion is used. This
// ensures that all packages continue to run in the same selinux domain.
- if ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) {
- return sharedUserSetting.seInfoTargetSdkVersion;
+ if ((sharedUser != null) && (sharedUser.getPackages().size() != 0)) {
+ return sharedUser.getSeInfoTargetSdkVersion();
}
final ApplicationInfo appInfo = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) {
@@ -376,18 +377,18 @@
* the ApplicationInfo instance of the package.
*
* @param pkg object representing the package to be labeled.
- * @param sharedUserSetting if the app shares a sharedUserId, then this has the shared setting.
+ * @param sharedUser if the app shares a sharedUserId, then this has the shared setting.
* @param compatibility the PlatformCompat service to ask about state of compat changes.
* @return String representing the resulting seinfo.
*/
- public static String getSeInfo(AndroidPackage pkg, SharedUserSetting sharedUserSetting,
+ public static String getSeInfo(AndroidPackage pkg, SharedUserApi sharedUser,
PlatformCompat compatibility) {
- final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUserSetting,
+ final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUser,
compatibility);
// TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync.
// They currently can be if the sharedUser apps are signed with the platform key.
- final boolean isPrivileged = (sharedUserSetting != null)
- ? sharedUserSetting.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged();
+ final boolean isPrivileged = (sharedUser != null)
+ ? sharedUser.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged();
return getSeInfo(pkg, isPrivileged, targetSdkVersion);
}
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index 4345d51..b952f80 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -88,7 +88,7 @@
/**
* Notify listeners that this object has changed.
*/
- protected void onChanged() {
+ public void onChanged() {
PackageStateMutator.onPackageStateChanged();
dispatchChange(this);
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f21bc93..13a3c5b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1438,19 +1438,19 @@
}
}
- boolean isPermissionUpgradeNeededLPr(int userId) {
+ boolean isPermissionUpgradeNeeded(int userId) {
return mRuntimePermissionsPersistence.isPermissionUpgradeNeeded(userId);
}
- void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) {
+ void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprint(userId);
}
- int getDefaultRuntimePermissionsVersionLPr(int userId) {
+ int getDefaultRuntimePermissionsVersion(int userId) {
return mRuntimePermissionsPersistence.getVersion(userId);
}
- void setDefaultRuntimePermissionsVersionLPr(int version, int userId) {
+ void setDefaultRuntimePermissionsVersion(int version, int userId) {
mRuntimePermissionsPersistence.setVersion(version, userId);
}
@@ -4288,49 +4288,6 @@
return pkg.getCurrentEnabledStateLPr(classNameStr, userId);
}
- boolean setPackageStoppedStateLPw(PackageManagerService pm, String packageName,
- boolean stopped, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- if (DEBUG_STOPPED) {
- if (stopped) {
- RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
- Slog.i(TAG, "Stopping package " + packageName, e);
- }
- }
- if (pkgSetting.getStopped(userId) != stopped) {
- pkgSetting.setStopped(stopped, userId);
- if (pkgSetting.getNotLaunched(userId)) {
- if (pkgSetting.getInstallSource().installerPackageName != null) {
- pm.notifyFirstLaunch(pkgSetting.getPackageName(),
- pkgSetting.getInstallSource().installerPackageName, userId);
- }
- pkgSetting.setNotLaunched(false, userId);
- }
- return true;
- }
- return false;
- }
-
- void setHarmfulAppWarningLPw(String packageName, CharSequence warning, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- pkgSetting.setHarmfulAppWarning(userId, warning == null ? null : warning.toString());
- }
-
- String getHarmfulAppWarningLPr(String packageName, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- return pkgSetting.getHarmfulAppWarning(userId);
- }
-
/**
* Returns all users on the device, including pre-created and dying users.
*
@@ -4869,6 +4826,9 @@
date.setTime(pus.getFirstInstallTime());
pw.println(sdf.format(date));
+ pw.print(" uninstallReason=");
+ pw.println(userState.getUninstallReason());
+
if (userState.isSuspended()) {
pw.print(prefix);
pw.println(" Suspend params:");
@@ -5150,7 +5110,7 @@
}
}
- void dumpReadMessagesLPr(PrintWriter pw, DumpState dumpState) {
+ void dumpReadMessages(PrintWriter pw, DumpState dumpState) {
pw.println("Settings parse messages:");
pw.print(mReadMessages.toString());
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 2227a78..0638d5e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -42,12 +42,14 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemConfig;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
@@ -254,6 +256,7 @@
/**
* Given the library name, returns a list of shared libraries on all versions.
+ * TODO: Remove, this is used for live mutation outside of the defined commit path
*/
@GuardedBy("mPm.mLock")
@Override
@@ -262,6 +265,11 @@
return mSharedLibraries.get(libName);
}
+ @VisibleForTesting
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ return mSharedLibraries;
+ }
+
/**
* Returns the shared library with given library name and version number.
*/
@@ -286,18 +294,19 @@
return mStaticLibsByDeclaringPackage.get(declaringPackageName);
}
- @GuardedBy("mPm.mLock")
- private @Nullable PackageSetting getLibraryPackageLPr(@NonNull SharedLibraryInfo libInfo) {
+ @Nullable
+ private PackageStateInternal getLibraryPackage(@NonNull Computer computer,
+ @NonNull SharedLibraryInfo libInfo) {
final VersionedPackage declaringPackage = libInfo.getDeclaringPackage();
if (libInfo.isStatic()) {
// Resolve the package name - we use synthetic package names internally
- final String internalPackageName = mPm.resolveInternalPackageNameLPr(
+ final String internalPackageName = computer.resolveInternalPackageName(
declaringPackage.getPackageName(),
declaringPackage.getLongVersionCode());
- return mPm.mSettings.getPackageLPr(internalPackageName);
+ return computer.getPackageStateInternal(internalPackageName);
}
if (libInfo.isSdk()) {
- return mPm.mSettings.getPackageLPr(declaringPackage.getPackageName());
+ return computer.getPackageStateInternal(declaringPackage.getPackageName());
}
return null;
}
@@ -317,24 +326,26 @@
final StorageManager storage = mInjector.getSystemService(StorageManager.class);
final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
- List<VersionedPackage> packagesToDelete = null;
+ final ArrayList<VersionedPackage> packagesToDelete = new ArrayList<>();
final long now = System.currentTimeMillis();
// Important: We skip shared libs used for some user since
// in such a case we need to keep the APK on the device. The check for
// a lib being used for any user is performed by the uninstall call.
- synchronized (mPm.mLock) {
- final int libCount = mSharedLibraries.size();
+ mPm.executeWithConsistentComputer(computer -> {
+ final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+ sharedLibraries = computer.getSharedLibraries();
+ final int libCount = sharedLibraries.size();
for (int i = 0; i < libCount; i++) {
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
- mSharedLibraries.valueAt(i);
+ sharedLibraries.valueAt(i);
if (versionedLib == null) {
continue;
}
final int versionCount = versionedLib.size();
for (int j = 0; j < versionCount; j++) {
SharedLibraryInfo libInfo = versionedLib.valueAt(j);
- final PackageSetting ps = getLibraryPackageLPr(libInfo);
+ final PackageStateInternal ps = getLibraryPackage(computer, libInfo);
if (ps == null) {
continue;
}
@@ -348,27 +359,22 @@
continue;
}
- if (packagesToDelete == null) {
- packagesToDelete = new ArrayList<>();
- }
packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(),
libInfo.getDeclaringPackage().getLongVersionCode()));
}
}
- }
+ });
- if (packagesToDelete != null) {
- final int packageCount = packagesToDelete.size();
- for (int i = 0; i < packageCount; i++) {
- final VersionedPackage pkgToDelete = packagesToDelete.get(i);
- // Delete the package synchronously (will fail of the lib used for any user).
- if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(),
- pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM,
- PackageManager.DELETE_ALL_USERS,
- true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
- if (volume.getUsableSpace() >= neededSpace) {
- return true;
- }
+ final int packageCount = packagesToDelete.size();
+ for (int i = 0; i < packageCount; i++) {
+ final VersionedPackage pkgToDelete = packagesToDelete.get(i);
+ // Delete the package synchronously (will fail of the lib used for any user).
+ if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(),
+ pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM,
+ PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
+ if (volume.getUsableSpace() >= neededSpace) {
+ return true;
}
}
}
@@ -525,7 +531,7 @@
@NonNull Map<String, AndroidPackage> availablePackages)
throws PackageManagerException {
final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
- pkgSetting.getPkg(), availablePackages, null /* newLibraries */);
+ pkg, availablePackages, null /* newLibraries */);
executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting,
sharedLibraryInfos, mPm.mUserManager.getUserIds());
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesRead.java b/services/core/java/com/android/server/pm/SharedLibrariesRead.java
index e6f2311..84d7478f 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesRead.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesRead.java
@@ -30,7 +30,7 @@
* An interface implemented by {@link SharedLibrariesImpl} for {@link Computer} to get current
* shared libraries on the device.
*/
-interface SharedLibrariesRead {
+public interface SharedLibrariesRead {
/**
* Returns all shared libraries on the device.
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index bc48461..5ef1471 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
+import android.content.pm.SigningDetails;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedProcess;
import com.android.server.pm.pkg.component.ParsedProcessImpl;
@@ -28,6 +29,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.utils.SnapshotCache;
import libcore.util.EmptyArray;
@@ -40,25 +43,26 @@
/**
* Settings data for a particular shared user ID we know about.
*/
-public final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase implements SharedUserApi {
final String name;
int userId;
- // flags that are associated with this uid, regardless of any package flags
+ /** @see SharedUserApi#getUidFlags() **/
int uidFlags;
int uidPrivateFlags;
- // The lowest targetSdkVersion of all apps in the sharedUserSetting, used to assign seinfo so
- // that all apps within the sharedUser run in the same selinux context.
+ /** @see SharedUserApi#getSeInfoTargetSdkVersion() **/
int seInfoTargetSdkVersion;
final ArraySet<PackageSetting> packages;
+ private ArraySet<PackageStateInternal> mPackagesSnapshot;
// It is possible for a system app to leave shared user ID by an update.
// We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
// the update and revert the system app back into the original shared user ID.
final ArraySet<PackageSetting> mDisabledPackages;
+ private ArraySet<PackageStateInternal> mDisabledPackagesSnapshot;
final PackageSignatures signatures = new PackageSignatures();
Boolean signaturesChanged;
@@ -98,7 +102,19 @@
uidFlags = orig.uidFlags;
uidPrivateFlags = orig.uidPrivateFlags;
packages = new ArraySet<>(orig.packages);
+ if (!packages.isEmpty()) {
+ mPackagesSnapshot = new ArraySet<>();
+ for (int index = 0; index < packages.size(); index++) {
+ mPackagesSnapshot.add(new PackageSetting(packages.valueAt(index)));
+ }
+ }
mDisabledPackages = new ArraySet<>(orig.mDisabledPackages);
+ if (!mDisabledPackages.isEmpty()) {
+ mDisabledPackagesSnapshot = new ArraySet<>();
+ for (int index = 0; index < mDisabledPackages.size(); index++) {
+ mDisabledPackagesSnapshot.add(new PackageSetting(mDisabledPackages.valueAt(index)));
+ }
+ }
// A SigningDetails seems to consist solely of final attributes, so
// it is safe to copy the reference.
signatures.mSigningDetails = orig.signatures.mSigningDetails;
@@ -184,10 +200,9 @@
}
}
- /**
- * @return the list of packages that uses this shared UID
- */
- public @NonNull List<AndroidPackage> getPackages() {
+ @NonNull
+ @Override
+ public List<AndroidPackage> getPackages() {
if (packages == null || packages.size() == 0) {
return Collections.emptyList();
}
@@ -201,6 +216,7 @@
return pkgList;
}
+ @Override
public boolean isPrivileged() {
return (this.getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
@@ -291,4 +307,60 @@
onChanged();
return this;
}
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getUserId() {
+ return userId;
+ }
+
+ @Override
+ public int getUidFlags() {
+ return uidFlags;
+ }
+
+ @Override
+ public int getPrivateUidFlags() {
+ return uidPrivateFlags;
+ }
+
+ @Override
+ public int getSeInfoTargetSdkVersion() {
+ return seInfoTargetSdkVersion;
+ }
+
+ @NonNull
+ @Override
+ public ArraySet<? extends PackageStateInternal> getPackageStates() {
+ if (mPackagesSnapshot != null) {
+ return mPackagesSnapshot;
+ }
+ return packages;
+ }
+
+ @NonNull
+ @Override
+ public ArraySet<? extends PackageStateInternal> getDisabledPackageStates() {
+ if (mDisabledPackagesSnapshot != null) {
+ return mDisabledPackagesSnapshot;
+ }
+ return mDisabledPackages;
+ }
+
+ @NonNull
+ @Override
+ public SigningDetails getSigningDetails() {
+ return signatures.mSigningDetails;
+ }
+
+ @NonNull
+ @Override
+ public ArrayMap<String, ParsedProcess> getProcesses() {
+ return processes;
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 72db242..bda2589 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1694,15 +1694,6 @@
for (int j = 0; j < shareTargetSize; j++) {
mShareTargets.get(j).saveToXml(out);
}
- synchronized (mLock) {
- final Map<String, ShortcutInfo> copy = mShortcuts;
- if (!mTransientShortcuts.isEmpty()) {
- copy.putAll(mTransientShortcuts);
- mTransientShortcuts.clear();
- }
- saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect(
- Collectors.toList()));
- }
}
out.endTag(null, TAG_ROOT);
@@ -2418,6 +2409,18 @@
})));
}
+ void persistsAllShortcutsAsync() {
+ synchronized (mLock) {
+ final Map<String, ShortcutInfo> copy = mShortcuts;
+ if (!mTransientShortcuts.isEmpty()) {
+ copy.putAll(mTransientShortcuts);
+ mTransientShortcuts.clear();
+ }
+ saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect(
+ Collectors.toList()));
+ }
+ }
+
private void saveShortcutsAsync(
@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
@@ -2489,7 +2492,7 @@
mIsAppSearchSchemaUpToDate = true;
}
} catch (Exception e) {
- Slog.e(TAG, "Failed to invoke app search pkg="
+ Slog.e(TAG, "Failed to create app search session. pkg="
+ getPackageName() + " user=" + mShortcutUser.getUserId(), e);
Objects.requireNonNull(future).completeExceptionally(e);
} finally {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8393dee..2760578 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -488,7 +488,7 @@
mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false);
+ SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true);
if (onlyForPackageManagerApis) {
return; // Don't do anything further. For unit tests only.
@@ -736,7 +736,7 @@
Slog.d(TAG, "unloadUserLocked: user=" + userId);
}
// Save all dirty information.
- saveDirtyInfo();
+ saveDirtyInfo(false);
// Unload
mUsers.delete(userId);
@@ -1191,6 +1191,10 @@
@VisibleForTesting
void saveDirtyInfo() {
+ saveDirtyInfo(true);
+ }
+
+ private void saveDirtyInfo(boolean saveShortcutsInAppSearch) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "saveDirtyInfo");
}
@@ -1205,6 +1209,10 @@
if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
saveBaseStateLocked();
} else {
+ if (saveShortcutsInAppSearch) {
+ getUserShortcutsLocked(userId).forAllPackages(
+ ShortcutPackage::persistsAllShortcutsAsync);
+ }
saveUserLocked(userId);
}
}
@@ -3719,13 +3727,16 @@
private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (DEBUG || DEBUG_REBOOT) {
+ Slog.d(TAG, "Shutdown broadcast received.");
+ }
// Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
// in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
// We need it so that it can finish up saving before shutdown.
synchronized (mLock) {
if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
mHandler.removeCallbacks(mSaveDirtyInfoRunner);
- saveDirtyInfo();
+ saveDirtyInfo(false);
}
mShutdown.set(true);
}
@@ -4394,7 +4405,7 @@
// Save to the filesystem.
scheduleSaveUser(userId);
- saveDirtyInfo();
+ saveDirtyInfo(false);
// Note, in case of backup, we don't have to wait on bitmap saving, because we don't
// back up bitmaps anyway.
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 29de555..f63f8f4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
@@ -237,7 +237,7 @@
mApexManager.revertActiveSessions();
}
- PackageHelper.getStorageManager().abortChanges(
+ InstallLocationUtils.getStorageManager().abortChanges(
"abort-staged-install", false /*retry*/);
}
} catch (Exception e) {
@@ -674,8 +674,8 @@
boolean needsCheckpoint = false;
boolean supportsCheckpoint = false;
try {
- supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
- needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+ supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
+ needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
} catch (RemoteException e) {
// This means that vold has crashed, and device is in a bad state.
throw new IllegalStateException("Failed to get checkpoint status", e);
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index f466ca7..1ea8b24 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -46,14 +46,17 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
public final class SuspendPackageHelper {
@@ -107,57 +110,87 @@
return packageNames;
}
+ final SuspendParams newSuspendParams =
+ SuspendParams.getInstanceOrNull(dialogInfo, appExtras, launcherExtras);
+
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
- final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
final IntArray modifiedUids = new IntArray(packageNames.length);
- final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
- final boolean[] canSuspend =
- suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null;
+ final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
- for (int i = 0; i < packageNames.length; i++) {
- final String packageName = packageNames[i];
- if (callingPackage.equals(packageName)) {
- Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
- + (suspended ? "" : "un") + "suspend itself. Ignoring");
- unactionedPackages.add(packageName);
- continue;
- }
- final PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkgSetting = mPm.mSettings.getPackageLPr(packageName);
- if (pkgSetting == null
- || mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) {
- Slog.w(TAG, "Could not find package setting for package: " + packageName
- + ". Skipping suspending/un-suspending.");
- unactionedPackages.add(packageName);
+ ArraySet<String> modifiedPackages = new ArraySet<>();
+
+ mPm.executeWithConsistentComputer(computer -> {
+ final boolean[] canSuspend = suspended
+ ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null;
+ for (int i = 0; i < packageNames.length; i++) {
+ final String packageName = packageNames[i];
+ if (callingPackage.equals(packageName)) {
+ Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+ + (suspended ? "" : "un") + "suspend itself. Ignoring");
+ unmodifiablePackages.add(packageName);
continue;
}
- }
- if (canSuspend != null && !canSuspend[i]) {
- unactionedPackages.add(packageName);
- continue;
- }
- final boolean packageUnsuspended;
- final boolean packageModified;
- synchronized (mPm.mLock) {
- if (suspended) {
- packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
- dialogInfo, appExtras, launcherExtras, userId);
- } else {
- packageModified = pkgSetting.removeSuspension(callingPackage, userId);
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ if (packageState == null
+ || computer.shouldFilterApplication(packageState, callingUid, userId)) {
+ Slog.w(TAG, "Could not find package setting for package: " + packageName
+ + ". Skipping suspending/un-suspending.");
+ unmodifiablePackages.add(packageName);
+ continue;
}
- packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
+ if (canSuspend != null && !canSuspend[i]) {
+ unmodifiablePackages.add(packageName);
+ continue;
+ }
+
+ final ArrayMap<String, SuspendParams> suspendParamsMap =
+ packageState.getUserStateOrDefault(userId).getSuspendParams();
+ final SuspendParams suspendParams = suspendParamsMap == null
+ ? null : suspendParamsMap.get(packageName);
+ boolean hasSuspension = suspendParams != null;
+ if (suspended) {
+ if (hasSuspension) {
+ // Skip if there's no changes
+ if (Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
+ && Objects.equals(suspendParams.getAppExtras(), appExtras)
+ && Objects.equals(suspendParams.getLauncherExtras(),
+ launcherExtras)) {
+ // Carried over API behavior, must notify change even if no change
+ changedPackagesList.add(packageName);
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ continue;
+ }
+ }
+ }
+
+ // If size one, the package will be unsuspended from this call
+ boolean packageUnsuspended =
+ !suspended && CollectionUtils.size(suspendParamsMap) <= 1;
+ if (suspended || packageUnsuspended) {
+ changedPackagesList.add(packageName);
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ }
+
+ modifiedPackages.add(packageName);
+ modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
}
- if (suspended || packageUnsuspended) {
- changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+ });
+
+ mPm.commitPackageStateMutation(null, mutator -> {
+ final int size = modifiedPackages.size();
+ for (int index = 0; index < size; index++) {
+ final String packageName = modifiedPackages.valueAt(index);
+ final PackageUserStateWrite userState = mutator.forPackage(packageName)
+ .userState(userId);
+ if (suspended) {
+ userState.putSuspendParams(callingPackage, newSuspendParams);
+ } else {
+ userState.removeSuspension(callingPackage);
+ }
}
- if (packageModified) {
- modifiedPackagesList.add(packageName);
- modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
- }
- }
+ });
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
@@ -166,16 +199,14 @@
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
- if (!modifiedPackagesList.isEmpty()) {
+ if (!modifiedPackages.isEmpty()) {
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
- modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+ modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
}
- return unactionedPackages.toArray(new String[0]);
+ return unmodifiablePackages.toArray(new String[0]);
}
/**
@@ -194,20 +225,22 @@
return packageNames;
}
final ArraySet<String> unactionablePackages = new ArraySet<>();
- final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid);
- for (int i = 0; i < packageNames.length; i++) {
- if (!canSuspend[i]) {
- unactionablePackages.add(packageNames[i]);
- continue;
- }
- synchronized (mPm.mLock) {
- final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]);
- if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) {
+ mPm.executeWithConsistentComputer(computer -> {
+ final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId,
+ callingUid);
+ for (int i = 0; i < packageNames.length; i++) {
+ if (!canSuspend[i]) {
+ unactionablePackages.add(packageNames[i]);
+ continue;
+ }
+ final PackageStateInternal packageState =
+ computer.getPackageStateFiltered(packageNames[i], callingUid, userId);
+ if (packageState == null) {
Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
unactionablePackages.add(packageNames[i]);
}
}
- }
+ });
return unactionablePackages.toArray(new String[unactionablePackages.size()]);
}
@@ -253,19 +286,53 @@
@NonNull Predicate<String> suspendingPackagePredicate, int userId) {
final List<String> unsuspendedPackages = new ArrayList<>();
final IntArray unsuspendedUids = new IntArray();
- synchronized (mPm.mLock) {
+ final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
+ mPm.executeWithConsistentComputer(computer -> {
for (String packageName : packagesToChange) {
- final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
- if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
- ps.removeSuspension(suspendingPackagePredicate, userId);
- if (!ps.getUserStateOrDefault(userId).isSuspended()) {
- unsuspendedPackages.add(ps.getPackageName());
- unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ final PackageUserStateInternal packageUserState = packageState == null
+ ? null : packageState.getUserStateOrDefault(userId);
+ if (packageUserState == null || !packageUserState.isSuspended()) {
+ continue;
+ }
+
+ ArrayMap<String, SuspendParams> suspendParamsMap = packageUserState.getSuspendParams();
+ int countRemoved = 0;
+ for (int index = 0; index < suspendParamsMap.size(); index++) {
+ String suspendingPackage = suspendParamsMap.keyAt(index);
+ if (suspendingPackagePredicate.test(suspendingPackage)) {
+ ArraySet<String> suspendingPkgsToCommit =
+ pkgToSuspendingPkgsToCommit.get(packageName);
+ if (suspendingPkgsToCommit == null) {
+ suspendingPkgsToCommit = new ArraySet<>();
+ pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit);
+ }
+ suspendingPkgsToCommit.add(suspendingPackage);
+ countRemoved++;
}
}
+
+ // Everything would be removed and package unsuspended
+ if (countRemoved == suspendParamsMap.size()) {
+ unsuspendedPackages.add(packageState.getPackageName());
+ unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ }
}
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ });
+
+ mPm.commitPackageStateMutation(null, mutator -> {
+ for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
+ String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
+ ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
+ PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
+ for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
+ userState.removeSuspension(packagesToRemove.valueAt(setIndex));
+ }
+ }
+ });
+
+ mPm.scheduleWritePackageRestrictions(userId);
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
@@ -406,7 +473,8 @@
* @return An array containing results of the checks
*/
@NonNull
- boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) {
+ boolean[] canSuspendPackageForUser(@NonNull Computer computer, @NonNull String[] packageNames,
+ int userId, int callingUid) {
final boolean[] canSuspend = new boolean[packageNames.length];
final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid);
final long token = Binder.clearCallingIdentity();
@@ -459,36 +527,38 @@
+ "\": required for permissions management");
continue;
}
- synchronized (mPm.mLock) {
- if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
- Slog.w(TAG, "Cannot suspend package \"" + packageName
- + "\": protected package");
- continue;
- }
- if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) {
- Slog.w(TAG, "Cannot suspend package \"" + packageName
- + "\": blocked by admin");
- continue;
- }
+ if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+ Slog.w(TAG, "Cannot suspend package \"" + packageName
+ + "\": protected package");
+ continue;
+ }
+ if (!isCallerOwner && computer.getBlockUninstall(userId, packageName)) {
+ Slog.w(TAG, "Cannot suspend package \"" + packageName
+ + "\": blocked by admin");
+ continue;
+ }
- AndroidPackage pkg = mPm.mPackages.get(packageName);
- if (pkg != null) {
- // Cannot suspend SDK libs as they are controlled by SDK manager.
- if (pkg.isSdkLibrary()) {
- Slog.w(TAG, "Cannot suspend package: " + packageName
- + " providing SDK library: "
- + pkg.getSdkLibName());
- continue;
- }
- // Cannot suspend static shared libs as they are considered
- // a part of the using app (emulating static linking). Also
- // static libs are installed always on internal storage.
- if (pkg.isStaticSharedLibrary()) {
- Slog.w(TAG, "Cannot suspend package: " + packageName
- + " providing static shared library: "
- + pkg.getStaticSharedLibName());
- continue;
- }
+ // Cannot suspend static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ PackageStateInternal packageState = computer.getPackageStateInternal(packageName);
+ AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
+ if (pkg != null) {
+ // Cannot suspend SDK libs as they are controlled by SDK manager.
+ if (pkg.isSdkLibrary()) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName
+ + " providing SDK library: "
+ + pkg.getSdkLibName());
+ continue;
+ }
+ // Cannot suspend static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ if (pkg.isStaticSharedLibrary()) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName
+ + " providing static shared library: "
+ + pkg.getStaticSharedLibName());
+ continue;
}
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
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..652080a 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,40 @@
@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);
+ }
+
+ @Override
+ public int getRemainingCreatableProfileCount(@NonNull String userType, @UserIdInt int userId) {
+ return getRemainingCreatableProfileCount(userType, userId, false);
+ }
+
+ /**
+ * 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)
+ */
+ private 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 +2554,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 +3859,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/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 1fa9013..9bcb724 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -285,6 +285,19 @@
);
/**
+ * User restrictions available to a device owner whose type is
+ * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_OUTGOING_CALLS
+ );
+
+ /**
* Returns whether the given restriction name is valid (and logs it if it isn't).
*/
public static boolean isValidRestriction(@NonNull String restriction) {
@@ -458,6 +471,15 @@
}
/**
+ * @return {@code true} only if the restriction is allowed for financed devices and can be set
+ * by a device owner. Otherwise, {@code false} would be returned.
+ */
+ public static boolean canFinancedDeviceOwnerChange(String restriction) {
+ return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction)
+ && canDeviceOwnerChange(restriction);
+ }
+
+ /**
* Whether given user restriction should be enforced globally.
*/
public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType,
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index a1a6f5a..9fb1f8f 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -30,11 +30,13 @@
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.SparseArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -211,31 +213,66 @@
Slog.i(TAG, "Reviewing whitelisted packages due to "
+ (isFirstBoot ? "[firstBoot]" : "") + (isConsideredUpgrade ? "[upgrade]" : ""));
final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+ // User ID -> package name -> installed
+ SparseArrayMap<String, Boolean> changesToCommit = new SparseArrayMap<>();
+
// Install/uninstall system packages per user.
for (int userId : mUm.getUserIds()) {
final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
- pmInt.forEachPackageSetting(pkgSetting -> {
- AndroidPackage pkg = pkgSetting.getPkg();
- if (pkg == null || !pkg.isSystem()) {
- return;
+
+ // If null, run for all packages
+ if (userWhitelist == null) {
+ pmInt.forEachPackageState(packageState -> {
+ if (packageState.getPkg() == null) {
+ return;
+ }
+ final boolean install = !packageState.getTransientState()
+ .isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
+ });
+ } else {
+ for (String packageName : userWhitelist) {
+ PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName);
+ if (packageState.getPkg() == null) {
+ continue;
+ }
+
+ final boolean install = !packageState.getTransientState()
+ .isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
}
- final boolean install =
- (userWhitelist == null || userWhitelist.contains(pkg.getPackageName()))
- && !pkgSetting.getPkgState().isHiddenUntilInstalled();
- if (pkgSetting.getInstalled(userId) == install
- || !shouldChangeInstallationState(pkgSetting, install, userId, isFirstBoot,
- isConsideredUpgrade, preExistingPackages)) {
- return;
- }
- pkgSetting.setInstalled(install, userId);
- pkgSetting.setUninstallReason(
- install ? PackageManager.UNINSTALL_REASON_UNKNOWN :
- PackageManager.UNINSTALL_REASON_USER_TYPE,
- userId);
- Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
- + pkg.getPackageName() + " for user " + userId);
- });
+ }
}
+
+ pmInt.commitPackageStateMutation(null, packageStateMutator -> {
+ for (int userIndex = 0; userIndex < changesToCommit.numMaps(); userIndex++) {
+ int userId = changesToCommit.keyAt(userIndex);
+ int packagesSize = changesToCommit.numElementsForKey(userId);
+ for (int packageIndex = 0; packageIndex < packagesSize; ++packageIndex) {
+ String packageName = changesToCommit.keyAt(userIndex, packageIndex);
+ boolean installed = changesToCommit.valueAt(userIndex, packageIndex);
+ packageStateMutator.forPackage(packageName)
+ .userState(userId)
+ .setInstalled(installed)
+ .setUninstallReason(installed
+ ? PackageManager.UNINSTALL_REASON_UNKNOWN
+ : PackageManager.UNINSTALL_REASON_USER_TYPE);
+
+ Slog.i(TAG + "CommitDebug", (installed ? "Installed " : "Uninstalled ")
+ + packageName + " for user " + userId);
+ }
+ }
+ });
+
return true;
}
@@ -250,7 +287,7 @@
* @param preOtaPkgs list of packages on the device prior to the upgrade.
* Cannot be null if isUpgrade is true.
*/
- private static boolean shouldChangeInstallationState(PackageSetting pkgSetting,
+ private static boolean shouldChangeInstallationState(PackageStateInternal packageState,
boolean install,
@UserIdInt int userId,
boolean isFirstBoot,
@@ -258,11 +295,12 @@
@Nullable ArraySet<String> preOtaPkgs) {
if (install) {
// Only proceed with install if we are the only reason why it had been uninstalled.
- return pkgSetting.getUninstallReason(userId)
+ return packageState.getUserStateOrDefault(userId).getUninstallReason()
== PackageManager.UNINSTALL_REASON_USER_TYPE;
} else {
// Only proceed with uninstall if the package is new to the device.
- return isFirstBoot || (isUpgrade && !preOtaPkgs.contains(pkgSetting.getPackageName()));
+ return isFirstBoot
+ || (isUpgrade && !preOtaPkgs.contains(packageState.getPackageName()));
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 2d0a3ef..63469cb 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -34,19 +34,6 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
-import com.android.server.pm.pkg.component.ComponentParseUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.PackageUserStateUtils;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -60,6 +47,19 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.PackageUserStateUtils;
+import com.android.server.pm.pkg.component.ComponentParseUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedProcess;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import libcore.util.EmptyArray;
diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
index 564585b..97d526d 100644
--- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
@@ -18,10 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedComponent;
import android.util.Pair;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ParsedComponent;
/**
* For exposing internal fields to the rest of the server, enforcing that any overridden state from
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 7e59bd6..f2b1a71 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -92,7 +92,7 @@
AndroidPackageUtils.getAllCodePaths(pkg),
pkg.getSdkLibName(),
pkg.getSdkLibVersionMajor(),
- SharedLibraryInfo.TYPE_SDK,
+ SharedLibraryInfo.TYPE_SDK_PACKAGE,
new VersionedPackage(pkg.getManifestPackageName(),
pkg.getLongVersionCode()),
null, null, false /* isNative */);
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index d455be7..46fde4b 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -108,14 +108,18 @@
* </p>
* @param packageName The package to start a one-time permission session for
* @param timeoutMillis Number of milliseconds for an app to be in an inactive state
+ * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
+ * before ending the session. Set to -1 to use default value
+ * for the device.
* @param importanceToResetTimer The least important level to uid must be to reset the timer
* @param importanceToKeepSessionAlive The least important level the uid must be to keep the
- * session alive
+ * session alive
*
* @hide
*/
void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
- int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ long revokeAfterKilledDelayMillis, int importanceToResetTimer,
+ int importanceToKeepSessionAlive) {
int uid;
try {
uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -126,11 +130,15 @@
synchronized (mLock) {
PackageInactivityListener listener = mListeners.get(uid);
- if (listener == null) {
- listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
+ if (listener != null) {
+ listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
importanceToResetTimer, importanceToKeepSessionAlive);
- mListeners.put(uid, listener);
+ return;
}
+ listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
+ revokeAfterKilledDelayMillis, importanceToResetTimer,
+ importanceToKeepSessionAlive);
+ mListeners.put(uid, listener);
}
}
@@ -159,18 +167,6 @@
}
/**
- * The delay to wait before revoking on the event an app is terminated. Recommended to be long
- * enough so that apps don't lose permission on an immediate restart
- */
- private long getKilledDelayMillis(boolean isSelfRevokedPermissionSession) {
- if (isSelfRevokedPermissionSession) {
- return 0;
- }
- return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
- PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS);
- }
-
- /**
* Register to listen for Uids being uninstalled. This must be done outside of the
* PermissionManagerService lock.
*/
@@ -178,18 +174,6 @@
mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED));
}
- void setSelfRevokedPermissionSession(int uid) {
- synchronized (mLock) {
- PackageInactivityListener listener = mListeners.get(uid);
- if (listener == null) {
- Log.e(LOG_TAG, "Could not set session for uid " + uid
- + " as self-revoke session: session not found");
- return;
- }
- listener.setSelfRevokedPermissionSession();
- }
- }
-
/**
* A class which watches a package for inactivity and notifies the permission controller when
* the package becomes inactive
@@ -200,11 +184,11 @@
private final int mUid;
private final @NonNull String mPackageName;
- private final long mTimeout;
- private final int mImportanceToResetTimer;
- private final int mImportanceToKeepSessionAlive;
+ private long mTimeout;
+ private long mRevokeAfterKilledDelay;
+ private int mImportanceToResetTimer;
+ private int mImportanceToKeepSessionAlive;
- private boolean mIsSelfRevokedPermissionSession;
private boolean mIsAlarmSet;
private boolean mIsFinished;
@@ -218,16 +202,23 @@
private final Object mToken = new Object();
private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
- int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ long revokeAfterkilledDelay, int importanceToResetTimer,
+ int importanceToKeepSessionAlive) {
Log.i(LOG_TAG,
"Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
+ + " killedDelay=" + revokeAfterkilledDelay
+ " importanceToResetTimer=" + importanceToResetTimer
+ " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
mUid = uid;
mPackageName = packageName;
mTimeout = timeout;
+ mRevokeAfterKilledDelay = revokeAfterkilledDelay == -1
+ ? DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
+ DEFAULT_KILLED_DELAY_MILLIS)
+ : revokeAfterkilledDelay;
mImportanceToResetTimer = importanceToResetTimer;
mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
@@ -247,6 +238,28 @@
onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
}
+ public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
+ int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ synchronized (mInnerLock) {
+ mTimeout = Math.min(mTimeout, timeoutMillis);
+ mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
+ revokeAfterKilledDelayMillis == -1
+ ? DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PERMISSIONS,
+ PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
+ : revokeAfterKilledDelayMillis);
+ mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
+ mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
+ mImportanceToKeepSessionAlive);
+ Log.v(LOG_TAG,
+ "Updated params for " + mPackageName + ". timeout=" + mTimeout
+ + " killedDelay=" + mRevokeAfterKilledDelay
+ + " importanceToResetTimer=" + mImportanceToResetTimer
+ + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
+ onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
+ }
+ }
+
private void onImportanceChanged(int uid, int importance) {
if (uid != mUid) {
return;
@@ -271,7 +284,7 @@
}
onImportanceChanged(mUid, imp);
}
- }, mToken, getKilledDelayMillis(mIsSelfRevokedPermissionSession));
+ }, mToken, mRevokeAfterKilledDelay);
return;
}
if (importance > mImportanceToResetTimer) {
@@ -307,14 +320,6 @@
}
/**
- * Marks the session as a self-revoke session, which does not delay the revocation when
- * the app is restarting.
- */
- public void setSelfRevokedPermissionSession() {
- mIsSelfRevokedPermissionSession = true;
- }
-
- /**
* Set the alarm which will callback when the package is inactive
*/
@GuardedBy("mInnerLock")
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 317730a..695d6dd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.pm.permission;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
@@ -50,6 +51,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -66,7 +68,6 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
@@ -386,7 +387,8 @@
@Override
public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
- long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
+ int importanceToKeepSessionAlive) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
"Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -396,7 +398,8 @@
final long token = Binder.clearCallingIdentity();
try {
getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
- timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
+ timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
+ importanceToKeepSessionAlive);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -561,16 +564,7 @@
@Override
public void revokeOwnPermissionsOnKill(@NonNull String packageName,
@NonNull List<String> permissions) {
- final int callingUid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(callingUid);
- AndroidFuture<Void> future = new AndroidFuture<>();
- future.whenComplete((result, err) -> {
- if (err == null) {
- getOneTimePermissionUserManager(callingUserId)
- .setSelfRevokedPermissionSession(callingUid);
- }
- });
- mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions, future);
+ mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions);
}
@Override
@@ -608,6 +602,22 @@
}
@Override
+ public int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid) {
+ int granted = PermissionManagerService.this.checkUidPermission(uid,
+ POST_NOTIFICATIONS);
+ AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+ if (granted != PackageManager.PERMISSION_GRANTED
+ && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
+ int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(),
+ POST_NOTIFICATIONS, UserHandle.getUserId(uid));
+ if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return granted;
+ }
+
+ @Override
public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
@Nullable List<String> permissionNames) {
Objects.requireNonNull(packageName, "packageName");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 8e41c9b..ed351fd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -108,7 +108,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.os.RoSystemProperties;
@@ -1592,8 +1591,7 @@
}
@Override
- public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions,
- AndroidFuture<Void> callback) {
+ public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions) {
final int callingUid = Binder.getCallingUid();
int callingUserId = UserHandle.getUserId(callingUid);
int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId);
@@ -1608,8 +1606,7 @@
+ permName + " because it does not hold that permission");
}
}
- mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions,
- callback);
+ mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions);
}
private boolean mayManageRolePermission(int uid) {
@@ -4486,7 +4483,7 @@
@Override
public void readLegacyPermissionStateTEMP() {
final int[] userIds = getAllUserIds();
- mPackageManagerInt.forEachPackageSetting(ps -> {
+ mPackageManagerInt.forEachPackageState(ps -> {
final int appId = ps.getAppId();
final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 91c558b..3e28320 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -27,7 +27,6 @@
import android.permission.IOnPermissionsChangeListener;
import android.permission.PermissionManagerInternal;
-import com.android.internal.infra.AndroidFuture;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.FileDescriptor;
@@ -338,16 +337,16 @@
* <li>Each permission in {@code permissions} must be a runtime permission.
* </ul>
* <p>
- * For every permission in {@code permissions}, the entire permission group it belongs to will
- * be revoked. This revocation happens asynchronously and kills all processes running in the
- * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+ * Background permissions which have no corresponding foreground permission still granted once
+ * the revocation is effective will also be revoked.
+ * <p>
+ * This revocation happens asynchronously and kills all processes running in the same UID as
+ * {@code packageName}. It will be triggered once it is safe to do so.
*
* @param packageName The name of the package for which the permissions will be revoked.
* @param permissions List of permissions to be revoked.
- * @param callback Callback called when the revocation request has been completed.
*/
- void revokeOwnPermissionsOnKill(String packageName, List<String> permissions,
- AndroidFuture<Void> callback);
+ void revokeOwnPermissionsOnKill(String packageName, List<String> permissions);
/**
* Get whether you should show UI with rationale for requesting a permission. You should do this
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index d2c4ec4..812d7a0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -63,6 +63,17 @@
int checkUidPermission(int uid, @NonNull String permissionName);
/**
+ * Check whether a particular UID has been granted the POST_NOTIFICATIONS permission, or if
+ * access should be granted based on legacy access (currently symbolized by the REVIEW_REQUIRED
+ * permission flag
+ *
+ * @param uid the UID
+ * @return {@code PERMISSION_GRANTED} if the permission is granted, or legacy access is granted,
+ * {@code PERMISSION_DENIED} otherwise
+ */
+ int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid);
+
+ /**
* Adds a listener for runtime permission state (permissions or flags) changes.
*
* @param listener The listener.
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index 56f62ab..fb2fe1f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -24,7 +24,6 @@
import com.android.server.pm.InstallSource;
import com.android.server.pm.PackageKeySetData;
-import com.android.server.pm.SharedUserSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.LegacyPermissionState;
@@ -52,7 +51,7 @@
InstallSource getInstallSource();
@Nullable
- SharedUserSetting getSharedUser();
+ SharedUserApi getSharedUser();
// TODO: Remove this in favor of boolean APIs
int getFlags();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index ef21543..7bd720a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -40,6 +40,7 @@
* where they would be lost implicitly by re-generating the package object.
*/
@DataClass(genSetters = true, genConstructor = false, genBuilder = false)
+@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting"})
public class PackageStateUnserialized {
private boolean hiddenUntilInstalled;
@@ -58,6 +59,14 @@
@Nullable
private String overrideSeInfo;
+ // TODO: Remove in favor of finer grained change notification
+ @NonNull
+ private final PackageSetting mPackageSetting;
+
+ public PackageStateUnserialized(@NonNull PackageSetting packageSetting) {
+ mPackageSetting = packageSetting;
+ }
+
private long[] lazyInitLastPackageUsageTimeInMills() {
return new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT];
}
@@ -70,6 +79,7 @@
return this;
}
getLastPackageUsageTimeInMills()[reason] = time;
+ mPackageSetting.onChanged();
return this;
}
@@ -108,6 +118,7 @@
this.updatedSystemApp = other.updatedSystemApp;
this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
this.overrideSeInfo = other.overrideSeInfo;
+ mPackageSetting.onChanged();
}
public @NonNull List<SharedLibraryInfo> getNonNativeUsesLibraryInfos() {
@@ -115,8 +126,45 @@
.filter((l) -> !l.isNative()).collect(Collectors.toList());
}
+ public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
+ hiddenUntilInstalled = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
- // Code below generated by codegen v1.0.14.
+ public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
+ usesLibraryInfos = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setUsesLibraryFiles(@NonNull List<String> value) {
+ usesLibraryFiles = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setUpdatedSystemApp(boolean value) {
+ updatedSystemApp = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setLastPackageUsageTimeInMills(@NonNull long... value) {
+ lastPackageUsageTimeInMills = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setOverrideSeInfo(@Nullable String value) {
+ overrideSeInfo = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -169,52 +217,15 @@
}
@DataClass.Generated.Member
- public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
- hiddenUntilInstalled = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
- usesLibraryInfos = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, usesLibraryInfos);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUsesLibraryFiles(@NonNull List<String> value) {
- usesLibraryFiles = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, usesLibraryFiles);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUpdatedSystemApp(boolean value) {
- updatedSystemApp = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setLastPackageUsageTimeInMills(@NonNull long... value) {
- lastPackageUsageTimeInMills = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, lastPackageUsageTimeInMills);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setOverrideSeInfo(@Nullable String value) {
- overrideSeInfo = value;
- return this;
+ public @NonNull PackageSetting getPackageSetting() {
+ return mPackageSetting;
}
@DataClass.Generated(
- time = 1580422870209L,
- codegenVersion = "1.0.14",
+ time = 1642554781099L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
- inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\n @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
new file mode 100644
index 0000000..43eac53
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedProcess;
+
+import java.util.List;
+
+public interface SharedUserApi {
+
+ @NonNull
+ String getName();
+
+ @UserIdInt
+ int getUserId();
+
+ // flags that are associated with this uid, regardless of any package flags
+ int getUidFlags();
+ int getPrivateUidFlags();
+
+ // The lowest targetSdkVersion of all apps in the sharedUserSetting, used to assign seinfo so
+ // that all apps within the sharedUser run in the same selinux context.
+ int getSeInfoTargetSdkVersion();
+
+ /**
+ * @return the list of packages that uses this shared UID
+ */
+ @NonNull
+ List<AndroidPackage> getPackages();
+
+ @NonNull
+ ArraySet<? extends PackageStateInternal> getPackageStates();
+
+ // It is possible for a system app to leave shared user ID by an update.
+ // We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
+ // the update and revert the system app back into the original shared user ID.
+ @NonNull
+ ArraySet<? extends PackageStateInternal> getDisabledPackageStates();
+
+ @NonNull
+ SigningDetails getSigningDetails();
+
+ @NonNull
+ ArrayMap<String, ParsedProcess> getProcesses();
+
+ boolean isPrivileged();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 586d2c4..cf478b1 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -34,4 +34,7 @@
@Nullable
String getMaxSdkVersion();
+
+ int getInitOrder();
+
}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 1e427d0..167aba3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -48,18 +48,18 @@
@Nullable
private String maxSdkVersion;
+ private int initOrder;
+
public ParsedApexSystemServiceImpl() {
}
-
-
// 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/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -71,13 +71,15 @@
@NonNull String name,
@Nullable String jarPath,
@Nullable String minSdkVersion,
- @Nullable String maxSdkVersion) {
+ @Nullable String maxSdkVersion,
+ int initOrder) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
this.jarPath = jarPath;
this.minSdkVersion = minSdkVersion;
this.maxSdkVersion = maxSdkVersion;
+ this.initOrder = initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -103,6 +105,11 @@
}
@DataClass.Generated.Member
+ public int getInitOrder() {
+ return initOrder;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -129,6 +136,12 @@
}
@DataClass.Generated.Member
+ public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
+ initOrder = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<String> sParcellingForName =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedString.class);
@@ -187,6 +200,7 @@
sParcellingForJarPath.parcel(jarPath, dest, flags);
sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+ dest.writeInt(initOrder);
}
@Override
@@ -205,6 +219,7 @@
String _jarPath = sParcellingForJarPath.unparcel(in);
String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+ int _initOrder = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -212,6 +227,7 @@
this.jarPath = _jarPath;
this.minSdkVersion = _minSdkVersion;
this.maxSdkVersion = _maxSdkVersion;
+ this.initOrder = _initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -231,10 +247,10 @@
};
@DataClass.Generated(
- time = 1641431950080L,
+ time = 1643723578605L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
- inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java",
+ inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
index 38a6f5a35..ed9aa2e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
@@ -53,10 +53,13 @@
R.styleable.AndroidManifestApexSystemService_minSdkVersion);
String maxSdkVersion = sa.getString(
R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+ int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
systemService.setName(className)
.setMinSdkVersion(minSdkVersion)
- .setMaxSdkVersion(maxSdkVersion);
+ .setMaxSdkVersion(maxSdkVersion)
+ .setInitOrder(initOrder);
+
if (!TextUtils.isEmpty(jarPath)) {
systemService.setJarPath(jarPath);
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 35d4d9e..951ddfa 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
@@ -177,6 +178,13 @@
}
@Override
+ public void onChanged() {
+ if (mState != null) {
+ mState.onChanged();
+ }
+ }
+
+ @Override
public PackageStateWrite setLastPackageUsageTime(int reason, long timeInMillis) {
if (mState != null) {
mState.getTransientState().setLastPackageUsageTimeInMills(reason, timeInMillis);
@@ -217,6 +225,51 @@
return this;
}
+ @NonNull
+ @Override
+ public PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category) {
+ if (mState != null) {
+ mState.setCategoryOverride(category);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
+ if (mState != null) {
+ mState.setUpdateAvailable(updateAvailable);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setLoadingProgress(float progress) {
+ if (mState != null) {
+ mState.setLoadingProgress(progress);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo) {
+ if (mState != null) {
+ mState.getTransientState().setOverrideSeInfo(newSeInfo);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setInstaller(@NonNull String installerPackageName) {
+ if (mState != null) {
+ mState.setInstallerPackageName(installerPackageName);
+ }
+ return this;
+ }
+
private static class UserStateWriteWrapper implements PackageUserStateWrite {
@Nullable
@@ -328,6 +381,25 @@
}
return this;
}
+
+ @NonNull
+ @Override
+ public PackageUserStateWrite setSplashScreenTheme(@Nullable String theme) {
+ if (mUserState != null) {
+ mUserState.setSplashScreenTheme(theme);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+ if (mUserState != null) {
+ mUserState.overrideLabelAndIcon(componentName, nonLocalizedLabel, icon);
+ }
+ return null;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 585bece..1ac0b05 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -17,12 +17,16 @@
package com.android.server.pm.pkg.mutate;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.ArraySet;
public interface PackageStateWrite {
+ void onChanged();
+
@NonNull
PackageUserStateWrite userState(@UserIdInt int userId);
@@ -38,4 +42,19 @@
@NonNull
PackageStateWrite setMimeGroup(@NonNull String mimeGroup, @NonNull ArraySet<String> mimeTypes);
+
+ @NonNull
+ PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
+
+ @NonNull
+ PackageStateWrite setUpdateAvailable(boolean updateAvailable);
+
+ @NonNull
+ PackageStateWrite setLoadingProgress(float progress);
+
+ @NonNull
+ PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
+
+ @NonNull
+ PackageStateWrite setInstaller(@NonNull String installerPackageName);
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index e23a1b6..11d6d97 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
@@ -60,4 +61,11 @@
@NonNull
PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning);
+
+ @NonNull
+ PackageUserStateWrite setSplashScreenTheme(@Nullable String theme);
+
+ @NonNull
+ PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon);
}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
new file mode 100644
index 0000000..cea84b5
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -0,0 +1,608 @@
+/*
+ * 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.power;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Controls Low Power Standby state.
+ *
+ * Instantiated by {@link PowerManagerService} only if Low Power Standby is supported.
+ *
+ * <p>Low Power Standby is active when all of the following conditions are met:
+ * <ul>
+ * <li>Low Power Standby is enabled
+ * <li>The device is not interactive, and has been non-interactive for a given timeout
+ * <li>The device is not in a doze maintenance window
+ * </ul>
+ *
+ * <p>When Low Power Standby is active, the following restrictions are applied to applications
+ * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP}:
+ * <ul>
+ * <li>Network access is blocked
+ * <li>Wakelocks are disabled
+ * </ul>
+ *
+ * @hide
+ */
+public final class LowPowerStandbyController {
+ private static final String TAG = "LowPowerStandbyController";
+ private static final boolean DEBUG = false;
+ private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false;
+
+ private static final int MSG_STANDBY_TIMEOUT = 0;
+ private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1;
+ private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2;
+
+ private final Handler mHandler;
+ private final SettingsObserver mSettingsObserver;
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final Clock mClock;
+ private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired =
+ this::onStandbyTimeoutExpired;
+ private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
+ private final SparseBooleanArray mAllowlistUids = new SparseBooleanArray();
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_OFF:
+ onNonInteractive();
+ break;
+ case Intent.ACTION_SCREEN_ON:
+ onInteractive();
+ break;
+ case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+ onDeviceIdleModeChanged();
+ break;
+ }
+ }
+ };
+
+ @GuardedBy("mLock")
+ private AlarmManager mAlarmManager;
+ @GuardedBy("mLock")
+ private PowerManager mPowerManager;
+ @GuardedBy("mLock")
+ private boolean mSupportedConfig;
+ @GuardedBy("mLock")
+ private boolean mEnabledByDefaultConfig;
+ @GuardedBy("mLock")
+ private int mStandbyTimeoutConfig;
+
+ /** Whether Low Power Standby is enabled in Settings */
+ @GuardedBy("mLock")
+ private boolean mIsEnabled;
+
+ /**
+ * Whether Low Power Standby is currently active (enforcing restrictions).
+ */
+ @GuardedBy("mLock")
+ private boolean mIsActive;
+
+ /** Whether the device is currently interactive */
+ @GuardedBy("mLock")
+ private boolean mIsInteractive;
+
+ /** The time the device was last interactive, in {@link SystemClock#elapsedRealtime()}. */
+ @GuardedBy("mLock")
+ private long mLastInteractiveTimeElapsed;
+
+ /**
+ * Whether we are in device idle mode.
+ * During maintenance windows Low Power Standby is deactivated to allow
+ * apps to run maintenance tasks.
+ */
+ @GuardedBy("mLock")
+ private boolean mIsDeviceIdle;
+
+ /**
+ * Whether the device has entered idle mode since becoming non-interactive.
+ * In the initial non-idle period after turning the screen off, Low Power Standby is already
+ * allowed to become active. Later non-idle periods are treated as maintenance windows, during
+ * which Low Power Standby is deactivated to allow apps to run maintenance tasks.
+ */
+ @GuardedBy("mLock")
+ private boolean mIdleSinceNonInteractive;
+
+ /** Whether Low Power Standby restrictions should be active during doze maintenance mode. */
+ @GuardedBy("mLock")
+ private boolean mActiveDuringMaintenance;
+
+ /** Force Low Power Standby to be active. */
+ @GuardedBy("mLock")
+ private boolean mForceActive;
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /** Returns milliseconds since boot, including time spent in sleep. */
+ long elapsedRealtime();
+ }
+
+ public LowPowerStandbyController(Context context, Looper looper, Clock clock) {
+ mContext = context;
+ mHandler = new LowPowerStandbyHandler(looper);
+ mClock = clock;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ }
+
+ void systemReady() {
+ final Resources resources = mContext.getResources();
+ synchronized (mLock) {
+ mSupportedConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported);
+
+ if (!mSupportedConfig) {
+ return;
+ }
+
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+
+ mStandbyTimeoutConfig = resources.getInteger(
+ R.integer.config_lowPowerStandbyNonInteractiveTimeout);
+ mEnabledByDefaultConfig = resources.getBoolean(
+ R.bool.config_lowPowerStandbyEnabledByDefault);
+
+ mIsInteractive = mPowerManager.isInteractive();
+
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_STANDBY_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ updateSettingsLocked();
+
+ if (mIsEnabled) {
+ registerBroadcastReceiver();
+ }
+ }
+
+ LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService);
+ }
+
+ @GuardedBy("mLock")
+ private void updateSettingsLocked() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_STANDBY_ENABLED,
+ mEnabledByDefaultConfig ? 1 : 0) != 0;
+ mActiveDuringMaintenance = Settings.Global.getInt(resolver,
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ DEFAULT_ACTIVE_DURING_MAINTENANCE ? 1 : 0) != 0;
+
+ updateActiveLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateActiveLocked() {
+ final long now = mClock.elapsedRealtime();
+ final boolean standbyTimeoutExpired =
+ (now - mLastInteractiveTimeElapsed) >= mStandbyTimeoutConfig;
+ final boolean maintenanceMode = mIdleSinceNonInteractive && !mIsDeviceIdle;
+ final boolean newActive =
+ mForceActive || (mIsEnabled && !mIsInteractive && standbyTimeoutExpired
+ && (!maintenanceMode || mActiveDuringMaintenance));
+ if (DEBUG) {
+ Slog.d(TAG, "updateActiveLocked: mIsEnabled=" + mIsEnabled + ", mIsInteractive="
+ + mIsInteractive + ", standbyTimeoutExpired=" + standbyTimeoutExpired
+ + ", mIdleSinceNonInteractive=" + mIdleSinceNonInteractive + ", mIsDeviceIdle="
+ + mIsDeviceIdle + ", mActiveDuringMaintenance=" + mActiveDuringMaintenance
+ + ", mForceActive=" + mForceActive + ", mIsActive=" + mIsActive + ", newActive="
+ + newActive);
+ }
+ if (mIsActive != newActive) {
+ mIsActive = newActive;
+ if (DEBUG) {
+ Slog.d(TAG, "mIsActive changed, mIsActive=" + mIsActive);
+ }
+ enqueueNotifyActiveChangedLocked();
+ }
+ }
+
+ private void onNonInteractive() {
+ if (DEBUG) {
+ Slog.d(TAG, "onNonInteractive");
+ }
+ final long now = mClock.elapsedRealtime();
+ synchronized (mLock) {
+ mIsInteractive = false;
+ mIsDeviceIdle = false;
+ mLastInteractiveTimeElapsed = now;
+
+ if (mStandbyTimeoutConfig > 0) {
+ scheduleStandbyTimeoutAlarmLocked();
+ }
+
+ updateActiveLocked();
+ }
+ }
+
+ private void onInteractive() {
+ if (DEBUG) {
+ Slog.d(TAG, "onInteractive");
+ }
+
+ synchronized (mLock) {
+ cancelStandbyTimeoutAlarmLocked();
+ mIsInteractive = true;
+ mIsDeviceIdle = false;
+ mIdleSinceNonInteractive = false;
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleStandbyTimeoutAlarmLocked() {
+ final long nextAlarmTime = SystemClock.elapsedRealtime() + mStandbyTimeoutConfig;
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ nextAlarmTime, "LowPowerStandbyController.StandbyTimeout",
+ mOnStandbyTimeoutExpired, mHandler);
+ }
+
+ @GuardedBy("mLock")
+ private void cancelStandbyTimeoutAlarmLocked() {
+ mAlarmManager.cancel(mOnStandbyTimeoutExpired);
+ }
+
+ private void onDeviceIdleModeChanged() {
+ synchronized (mLock) {
+ mIsDeviceIdle = mPowerManager.isDeviceIdleMode();
+ if (DEBUG) {
+ Slog.d(TAG, "onDeviceIdleModeChanged, mIsDeviceIdle=" + mIsDeviceIdle);
+ }
+
+ mIdleSinceNonInteractive = mIdleSinceNonInteractive || mIsDeviceIdle;
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onEnabledLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "onEnabledLocked");
+ }
+
+ if (mPowerManager.isInteractive()) {
+ onInteractive();
+ } else {
+ onNonInteractive();
+ }
+
+ registerBroadcastReceiver();
+ }
+
+ @GuardedBy("mLock")
+ private void onDisabledLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "onDisabledLocked");
+ }
+
+ cancelStandbyTimeoutAlarmLocked();
+ unregisterBroadcastReceiver();
+ updateActiveLocked();
+ }
+
+ @VisibleForTesting
+ void onSettingsChanged() {
+ if (DEBUG) {
+ Slog.d(TAG, "onSettingsChanged");
+ }
+ synchronized (mLock) {
+ final boolean oldEnabled = mIsEnabled;
+ updateSettingsLocked();
+
+ if (mIsEnabled != oldEnabled) {
+ if (mIsEnabled) {
+ onEnabledLocked();
+ } else {
+ onDisabledLocked();
+ }
+
+ notifyEnabledChangedLocked();
+ }
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ @GuardedBy("mLock")
+ private void notifyEnabledChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled);
+ }
+
+ final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void onStandbyTimeoutExpired() {
+ if (DEBUG) {
+ Slog.d(TAG, "onStandbyTimeoutExpired");
+ }
+ synchronized (mLock) {
+ updateActiveLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void enqueueNotifyActiveChangedLocked() {
+ final long now = mClock.elapsedRealtime();
+ final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive);
+ mHandler.sendMessageAtTime(msg, now);
+ }
+
+ /** Notify other system components about the updated Low Power Standby active state */
+ private void notifyActiveChanged(boolean active) {
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ pmi.setLowPowerStandbyActive(active);
+ }
+
+ @VisibleForTesting
+ boolean isActive() {
+ synchronized (mLock) {
+ return mIsActive;
+ }
+ }
+
+ boolean isSupported() {
+ synchronized (mLock) {
+ return mSupportedConfig;
+ }
+ }
+
+ boolean isEnabled() {
+ synchronized (mLock) {
+ return mSupportedConfig && mIsEnabled;
+ }
+ }
+
+ void setEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (!mSupportedConfig) {
+ Slog.w(TAG, "Low Power Standby cannot be enabled "
+ + "because it is not supported on this device");
+ return;
+ }
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.LOW_POWER_STANDBY_ENABLED, enabled ? 1 : 0);
+ onSettingsChanged();
+ }
+ }
+
+ void setActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ synchronized (mLock) {
+ if (!mSupportedConfig) {
+ Slog.w(TAG, "Low Power Standby settings cannot be changed "
+ + "because it is not supported on this device");
+ return;
+ }
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE,
+ activeDuringMaintenance ? 1 : 0);
+ onSettingsChanged();
+ }
+ }
+
+ void forceActive(boolean active) {
+ synchronized (mLock) {
+ mForceActive = active;
+ updateActiveLocked();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+
+ ipw.println();
+ ipw.println("Low Power Standby Controller:");
+ ipw.increaseIndent();
+ synchronized (mLock) {
+ ipw.print("mIsActive=");
+ ipw.println(mIsActive);
+ ipw.print("mIsEnabled=");
+ ipw.println(mIsEnabled);
+ ipw.print("mSupportedConfig=");
+ ipw.println(mSupportedConfig);
+ ipw.print("mEnabledByDefaultConfig=");
+ ipw.println(mEnabledByDefaultConfig);
+ ipw.print("mStandbyTimeoutConfig=");
+ ipw.println(mStandbyTimeoutConfig);
+
+ if (mIsActive || mIsEnabled) {
+ ipw.print("mIsInteractive=");
+ ipw.println(mIsInteractive);
+ ipw.print("mLastInteractiveTime=");
+ ipw.println(mLastInteractiveTimeElapsed);
+ ipw.print("mIdleSinceNonInteractive=");
+ ipw.println(mIdleSinceNonInteractive);
+ ipw.print("mIsDeviceIdle=");
+ ipw.println(mIsDeviceIdle);
+ }
+
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ ipw.print("mAllowlistUids=");
+ ipw.println(Arrays.toString(allowlistUids));
+ }
+ ipw.decreaseIndent();
+ }
+
+ void dumpProto(ProtoOutputStream proto, long tag) {
+ synchronized (mLock) {
+ final long token = proto.start(tag);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ACTIVE, mIsActive);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED, mIsEnabled);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_SUPPORTED_CONFIG, mSupportedConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED_BY_DEFAULT_CONFIG,
+ mEnabledByDefaultConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_INTERACTIVE, mIsInteractive);
+ proto.write(LowPowerStandbyControllerDumpProto.LAST_INTERACTIVE_TIME,
+ mLastInteractiveTimeElapsed);
+ proto.write(LowPowerStandbyControllerDumpProto.STANDBY_TIMEOUT_CONFIG,
+ mStandbyTimeoutConfig);
+ proto.write(LowPowerStandbyControllerDumpProto.IDLE_SINCE_NON_INTERACTIVE,
+ mIdleSinceNonInteractive);
+ proto.write(LowPowerStandbyControllerDumpProto.IS_DEVICE_IDLE, mIsDeviceIdle);
+
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ for (int appId : allowlistUids) {
+ proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId);
+ }
+
+ proto.end(token);
+ }
+ }
+
+ private class LowPowerStandbyHandler extends Handler {
+ LowPowerStandbyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_STANDBY_TIMEOUT:
+ onStandbyTimeoutExpired();
+ break;
+ case MSG_NOTIFY_ACTIVE_CHANGED:
+ boolean active = (boolean) msg.obj;
+ notifyActiveChanged(active);
+ break;
+ case MSG_NOTIFY_ALLOWLIST_CHANGED:
+ final int[] allowlistUids = (int[]) msg.obj;
+ notifyAllowlistChanged(allowlistUids);
+ break;
+ }
+ }
+ }
+
+ private void addToAllowlistInternal(int uid) {
+ if (DEBUG) {
+ Slog.i(TAG, "Adding to allowlist: " + uid);
+ }
+ synchronized (mLock) {
+ if (mSupportedConfig && !mAllowlistUids.get(uid)) {
+ mAllowlistUids.append(uid, true);
+ enqueueNotifyAllowlistChangedLocked();
+ }
+ }
+ }
+
+ private void removeFromAllowlistInternal(int uid) {
+ if (DEBUG) {
+ Slog.i(TAG, "Removing from allowlist: " + uid);
+ }
+ synchronized (mLock) {
+ if (mSupportedConfig && mAllowlistUids.get(uid)) {
+ mAllowlistUids.delete(uid);
+ enqueueNotifyAllowlistChangedLocked();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int[] getAllowlistUidsLocked() {
+ final int[] uids = new int[mAllowlistUids.size()];
+ for (int i = 0; i < mAllowlistUids.size(); i++) {
+ uids[i] = mAllowlistUids.keyAt(i);
+ }
+ return uids;
+ }
+
+ @GuardedBy("mLock")
+ private void enqueueNotifyAllowlistChangedLocked() {
+ final long now = mClock.elapsedRealtime();
+ final int[] allowlistUids = getAllowlistUidsLocked();
+ final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids);
+ mHandler.sendMessageAtTime(msg, now);
+ }
+
+ private void notifyAllowlistChanged(int[] allowlistUids) {
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ pmi.setLowPowerStandbyAllowlist(allowlistUids);
+ }
+
+ private final class LocalService extends LowPowerStandbyControllerInternal {
+ @Override
+ public void addToAllowlist(int uid) {
+ addToAllowlistInternal(uid);
+ }
+
+ @Override
+ public void removeFromAllowlist(int uid) {
+ removeFromAllowlistInternal(uid);
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ onSettingsChanged();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
new file mode 100644
index 0000000..f6953fa
--- /dev/null
+++ b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java
@@ -0,0 +1,37 @@
+/*
+ * 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.power;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class LowPowerStandbyControllerInternal {
+ /**
+ * Adds an application to the Low Power Standby allowlist,
+ * exempting it from Low Power Standby restrictions.
+ *
+ * @param uid UID to add to allowlist.
+ */
+ public abstract void addToAllowlist(int uid);
+
+ /**
+ * Removes an application from the Low Power Standby allowlist.
+ *
+ * @param uid UID to remove from allowlist.
+ */
+ public abstract void removeFromAllowlist(int uid);
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 73ec2cd..77d6310 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -33,6 +33,7 @@
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Handler;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -215,14 +216,15 @@
* Called when a wake lock is acquired.
*/
public void onWakeLockAcquired(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+ IWakeLockCallback callback) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
-
+ notifyWakeLockListener(callback, true);
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
@@ -300,8 +302,9 @@
*/
public void onWakeLockChanging(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
- int newFlags, String newTag, String newPackageName, int newOwnerUid,
- int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
+ IWakeLockCallback callback, int newFlags, String newTag, String newPackageName,
+ int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag,
+ IWakeLockCallback newCallback) {
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
@@ -323,10 +326,16 @@
} catch (RemoteException ex) {
// Ignore
}
- } else {
- onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
+ } else if (!PowerManagerService.isSameCallback(callback, newCallback)) {
+ onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+ null /* Do not notify the old callback */);
onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
- newWorkSource, newHistoryTag);
+ newWorkSource, newHistoryTag, newCallback /* notify the new callback */);
+ } else {
+ onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag,
+ callback);
+ onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
+ newWorkSource, newHistoryTag, newCallback);
}
}
@@ -334,14 +343,15 @@
* Called when a wake lock is released.
*/
public void onWakeLockReleased(int flags, String tag, String packageName,
- int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
+ int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
+ IWakeLockCallback callback) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
-
+ notifyWakeLockListener(callback, false);
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
@@ -859,6 +869,18 @@
return enabled && dndOff;
}
+ private void notifyWakeLockListener(IWakeLockCallback callback, boolean isEnabled) {
+ if (callback != null) {
+ mHandler.post(() -> {
+ try {
+ callback.onStateChanged(isEnabled);
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Wakelock.mCallback is already dead.", e);
+ }
+ });
+ }
+ }
+
private final class NotifierHandler extends Handler {
public NotifierHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4185b2d..3857072 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -37,6 +37,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -65,6 +66,7 @@
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelDuration;
@@ -276,6 +278,7 @@
private final BatterySaverPolicy mBatterySaverPolicy;
private final BatterySaverStateMachine mBatterySaverStateMachine;
private final BatterySavingStats mBatterySavingStats;
+ private final LowPowerStandbyController mLowPowerStandbyController;
private final AttentionDetector mAttentionDetector;
private final FaceDownDetector mFaceDownDetector;
private final ScreenUndimDetector mScreenUndimDetector;
@@ -609,12 +612,17 @@
// True if we are currently in light device idle mode.
private boolean mLightDeviceIdleMode;
- // Set of app ids that we will always respect the wake locks for.
+ // Set of app ids that we will respect the wake locks for while in device idle mode.
int[] mDeviceIdleWhitelist = new int[0];
// Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message
int[] mDeviceIdleTempWhitelist = new int[0];
+ // Set of app ids that are allowed to acquire wakelocks while low power standby is active
+ int[] mLowPowerStandbyAllowlist = new int[0];
+
+ private boolean mLowPowerStandbyActive;
+
private final SparseArray<UidState> mUidState = new SparseArray<>();
// A mapping from DisplayGroup Id to PowerGroup. There is a 1-1 mapping between DisplayGroups
@@ -966,6 +974,10 @@
void invalidateIsInteractiveCaches() {
PowerManager.invalidateIsInteractiveCaches();
}
+
+ LowPowerStandbyController createLowPowerStandbyController(Context context, Looper looper) {
+ return new LowPowerStandbyController(context, looper, SystemClock::elapsedRealtime);
+ }
}
final Constants mConstants;
@@ -1015,6 +1027,8 @@
mBatterySaverStateMachine = mInjector.createBatterySaverStateMachine(mLock, mContext,
mBatterySaverController);
+ mLowPowerStandbyController = mInjector.createLowPowerStandbyController(mContext,
+ Looper.getMainLooper());
mInattentiveSleepWarningOverlayController =
mInjector.createInattentiveSleepWarningController();
@@ -1228,6 +1242,8 @@
// Shouldn't happen since in-process.
}
+ mLowPowerStandbyController.systemReady();
+
// Go.
readConfigurationLocked();
updateSettingsLocked();
@@ -1421,7 +1437,8 @@
}
private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
- String packageName, WorkSource ws, String historyTag, int uid, int pid) {
+ String packageName, WorkSource ws, String historyTag, int uid, int pid,
+ @Nullable IWakeLockCallback callback) {
synchronized (mLock) {
if (displayId != Display.INVALID_DISPLAY) {
final DisplayInfo displayInfo =
@@ -1445,11 +1462,12 @@
boolean notifyAcquire;
if (index >= 0) {
wakeLock = mWakeLocks.get(index);
- if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
+ if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid, callback)) {
// Update existing wake lock. This shouldn't happen but is harmless.
notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
- uid, pid, ws, historyTag);
- wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
+ uid, pid, ws, historyTag, callback);
+ wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid,
+ callback);
}
notifyAcquire = false;
} else {
@@ -1461,12 +1479,7 @@
}
state.mNumWakeLocks++;
wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
- uid, pid, state);
- try {
- lock.linkToDeath(wakeLock, 0);
- } catch (RemoteException ex) {
- throw new IllegalArgumentException("Wake lock is already dead.");
- }
+ uid, pid, state, callback);
mWakeLocks.add(wakeLock);
setWakeLockDisabledStateLocked(wakeLock);
notifyAcquire = true;
@@ -1561,11 +1574,8 @@
mRequestWaitForNegativeProximity = true;
}
- try {
- wakeLock.mLock.unlinkToDeath(wakeLock, 0);
- } catch (NoSuchElementException e) {
- Slog.wtf(TAG, "Failed to unlink wakelock", e);
- }
+ wakeLock.unlinkToDeath();
+ wakeLock.setDisabled(true);
removeWakeLockLocked(wakeLock, index);
}
}
@@ -1635,13 +1645,41 @@
if (!wakeLock.hasSameWorkSource(ws)) {
notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
- ws, historyTag);
+ ws, historyTag, null);
wakeLock.mHistoryTag = historyTag;
wakeLock.updateWorkSource(ws);
}
}
}
+ private void updateWakeLockCallbackInternal(IBinder lock, IWakeLockCallback callback,
+ int callingUid) {
+ synchronized (mLock) {
+ int index = findWakeLockIndexLocked(lock);
+ if (index < 0) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+ + " [not found]");
+ }
+ throw new IllegalArgumentException("Wake lock not active: " + lock
+ + " from uid " + callingUid);
+ }
+
+ WakeLock wakeLock = mWakeLocks.get(index);
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock)
+ + " [" + wakeLock.mTag + "]");
+ }
+
+ if (!isSameCallback(callback, wakeLock.mCallback)) {
+ notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag,
+ wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
+ wakeLock.mWorkSource, wakeLock.mHistoryTag, callback);
+ wakeLock.mCallback = callback;
+ }
+ }
+ }
+
@GuardedBy("mLock")
private int findWakeLockIndexLocked(IBinder lock) {
final int count = mWakeLocks.size();
@@ -1654,12 +1692,22 @@
}
@GuardedBy("mLock")
+ @VisibleForTesting
+ WakeLock findWakeLockLocked(IBinder lock) {
+ int index = findWakeLockIndexLocked(lock);
+ if (index == -1) {
+ return null;
+ }
+ return mWakeLocks.get(index);
+ }
+
+ @GuardedBy("mLock")
private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) {
if (mSystemReady && !wakeLock.mDisabled) {
wakeLock.mNotifiedAcquired = true;
mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
- wakeLock.mHistoryTag);
+ wakeLock.mHistoryTag, wakeLock.mCallback);
restartNofifyLongTimerLocked(wakeLock);
}
}
@@ -1701,11 +1749,13 @@
@GuardedBy("mLock")
private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag,
- String packageName, int uid, int pid, WorkSource ws, String historyTag) {
+ String packageName, int uid, int pid, WorkSource ws, String historyTag,
+ IWakeLockCallback callback) {
if (mSystemReady && wakeLock.mNotifiedAcquired) {
mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
- wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag);
+ wakeLock.mHistoryTag, wakeLock.mCallback, flags, tag, packageName, uid, pid, ws,
+ historyTag, callback);
notifyWakeLockLongFinishedLocked(wakeLock);
// Changing the wake lock will count as releasing the old wake lock(s) and
// acquiring the new ones... we do this because otherwise once a wakelock
@@ -1722,7 +1772,7 @@
wakeLock.mAcquireTime = 0;
mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag,
wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid,
- wakeLock.mWorkSource, wakeLock.mHistoryTag);
+ wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback);
notifyWakeLockLongFinishedLocked(wakeLock);
}
}
@@ -3852,6 +3902,24 @@
}
}
+ void setLowPowerStandbyAllowlistInternal(int[] appids) {
+ synchronized (mLock) {
+ mLowPowerStandbyAllowlist = appids;
+ if (mLowPowerStandbyActive) {
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
+ void setLowPowerStandbyActiveInternal(boolean active) {
+ synchronized (mLock) {
+ if (mLowPowerStandbyActive != active) {
+ mLowPowerStandbyActive = active;
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
void startUidChangesInternal() {
synchronized (mLock) {
mUidsChanging = true;
@@ -3888,7 +3956,7 @@
<= ActivityManager.PROCESS_STATE_RECEIVER;
state.mProcState = procState;
if (state.mNumWakeLocks > 0) {
- if (mDeviceIdleMode) {
+ if (mDeviceIdleMode || mLowPowerStandbyActive) {
handleUidStateChangeLocked();
} else if (!state.mActive && oldShouldAllow !=
(procState <= ActivityManager.PROCESS_STATE_RECEIVER)) {
@@ -3908,7 +3976,7 @@
state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
state.mActive = false;
mUidState.removeAt(index);
- if (mDeviceIdleMode && state.mNumWakeLocks > 0) {
+ if ((mDeviceIdleMode || mLowPowerStandbyActive) && state.mNumWakeLocks > 0) {
handleUidStateChangeLocked();
}
}
@@ -3993,11 +4061,16 @@
disabled = true;
}
}
+ if (mLowPowerStandbyActive) {
+ final UidState state = wakeLock.mUidState;
+ if (Arrays.binarySearch(mLowPowerStandbyAllowlist, appid) < 0
+ && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT
+ && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_TOP) {
+ disabled = true;
+ }
+ }
}
- if (wakeLock.mDisabled != disabled) {
- wakeLock.mDisabled = disabled;
- return true;
- }
+ return wakeLock.setDisabled(disabled);
}
return false;
}
@@ -4260,6 +4333,7 @@
pw.println("POWER MANAGER (dumpsys power)\n");
final WirelessChargerDetector wcd;
+ final LowPowerStandbyController lowPowerStandbyController;
synchronized (mLock) {
pw.println("Power Manager State:");
mConstants.dump(pw);
@@ -4316,6 +4390,7 @@
pw.println(" mDeviceIdleMode=" + mDeviceIdleMode);
pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
pw.println(" mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
+ pw.println(" mLowPowerStandbyActive=" + mLowPowerStandbyActive);
pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastGlobalWakeTime));
pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastGlobalSleepTime));
pw.println(" mLastSleepReason=" + PowerManager.sleepReasonToString(
@@ -4491,10 +4566,13 @@
mFaceDownDetector.dump(pw);
mAmbientDisplaySuppressionController.dump(pw);
+
+ mLowPowerStandbyController.dump(pw);
}
private void dumpProto(FileDescriptor fd) {
final WirelessChargerDetector wcd;
+ final LowPowerStandbyController lowPowerStandbyController;
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mLock) {
@@ -4599,6 +4677,9 @@
proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
}
+ proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_STANDBY_ACTIVE,
+ mLowPowerStandbyActive);
+
proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastGlobalWakeTime);
proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastGlobalSleepTime);
proto.write(
@@ -4832,6 +4913,7 @@
for (SuspendBlocker sb : mSuspendBlockers) {
sb.dumpDebug(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
}
+
wcd = mWirelessChargerDetector;
}
@@ -4839,6 +4921,9 @@
wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
}
+ mLowPowerStandbyController.dumpProto(proto,
+ PowerManagerServiceDumpProto.LOW_POWER_STANDBY_CONTROLLER);
+
proto.flush();
}
@@ -4978,10 +5063,11 @@
public boolean mNotifiedAcquired;
public boolean mNotifiedLong;
public boolean mDisabled;
+ public IWakeLockCallback mCallback;
public WakeLock(IBinder lock, int displayId, int flags, String tag, String packageName,
WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
- UidState uidState) {
+ UidState uidState, @Nullable IWakeLockCallback callback) {
mLock = lock;
mDisplayId = displayId;
mFlags = flags;
@@ -4992,15 +5078,43 @@
mOwnerUid = ownerUid;
mOwnerPid = ownerPid;
mUidState = uidState;
+ mCallback = callback;
+ linkToDeath();
}
@Override
public void binderDied() {
+ unlinkToDeath();
PowerManagerService.this.handleWakeLockDeath(this);
}
+ private void linkToDeath() {
+ try {
+ mLock.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Wakelock.mLock is already dead.");
+ }
+ }
+
+ @GuardedBy("mLock")
+ void unlinkToDeath() {
+ try {
+ mLock.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink Wakelock.mLock", e);
+ }
+ }
+
+ public boolean setDisabled(boolean disabled) {
+ if (mDisabled != disabled) {
+ mDisabled = disabled;
+ return true;
+ } else {
+ return false;
+ }
+ }
public boolean hasSameProperties(int flags, String tag, WorkSource workSource,
- int ownerUid, int ownerPid) {
+ int ownerUid, int ownerPid, IWakeLockCallback callback) {
return mFlags == flags
&& mTag.equals(tag)
&& hasSameWorkSource(workSource)
@@ -5009,7 +5123,8 @@
}
public void updateProperties(int flags, String tag, String packageName,
- WorkSource workSource, String historyTag, int ownerUid, int ownerPid) {
+ WorkSource workSource, String historyTag, int ownerUid, int ownerPid,
+ IWakeLockCallback callback) {
if (!mPackageName.equals(packageName)) {
throw new IllegalStateException("Existing wake lock package name changed: "
+ mPackageName + " to " + packageName);
@@ -5026,6 +5141,7 @@
mTag = tag;
updateWorkSource(workSource);
mHistoryTag = historyTag;
+ mCallback = callback;
}
public boolean hasSameWorkSource(WorkSource workSource) {
@@ -5092,6 +5208,8 @@
(mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP)!=0);
proto.write(WakeLockProto.WakeLockFlagsProto.IS_ON_AFTER_RELEASE,
(mFlags & PowerManager.ON_AFTER_RELEASE)!=0);
+ proto.write(WakeLockProto.WakeLockFlagsProto.SYSTEM_WAKELOCK,
+ (mFlags & PowerManager.SYSTEM_WAKELOCK) != 0);
proto.end(wakeLockFlagsToken);
proto.write(WakeLockProto.IS_DISABLED, mDisabled);
@@ -5138,6 +5256,9 @@
if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) {
result += " ON_AFTER_RELEASE";
}
+ if ((mFlags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ result += " SYSTEM_WAKELOCK";
+ }
return result;
}
}
@@ -5239,11 +5360,12 @@
@Override // Binder call
public void acquireWakeLockWithUid(IBinder lock, int flags, String tag,
- String packageName, int uid, int displayId) {
+ String packageName, int uid, int displayId, IWakeLockCallback callback) {
if (uid < 0) {
uid = Binder.getCallingUid();
}
- acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, displayId);
+ acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null,
+ displayId, callback);
}
@Override // Binder call
@@ -5278,7 +5400,8 @@
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws, String historyTag, int displayId) {
+ WorkSource ws, String historyTag, int displayId,
+ @Nullable IWakeLockCallback callback) {
if (lock == null) {
throw new IllegalArgumentException("lock must not be null");
}
@@ -5299,12 +5422,26 @@
ws = null;
}
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+
+ if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ WorkSource workSource = new WorkSource(Binder.getCallingUid(), packageName);
+ if (ws != null && !ws.isEmpty()) {
+ workSource.add(ws);
+ }
+ ws = workSource;
+
+ uid = Process.myUid();
+ pid = Process.myPid();
+ }
+
final long ident = Binder.clearCallingIdentity();
try {
acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
- uid, pid);
+ uid, pid, callback);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -5313,7 +5450,8 @@
@Override // Binder call
public void acquireWakeLockAsync(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag) {
- acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY);
+ acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY,
+ null);
}
@Override // Binder call
@@ -5381,6 +5519,23 @@
}
@Override // Binder call
+ public void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback) {
+ if (lock == null) {
+ throw new IllegalArgumentException("lock must not be null");
+ }
+
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ updateWakeLockCallbackInternal(lock, callback, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public boolean isWakeLockLevelSupported(int level) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -5768,6 +5923,100 @@
}
}
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public boolean isLowPowerStandbySupported() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "isLowPowerStandbySupported");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mLowPowerStandbyController.isSupported();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean isLowPowerStandbyEnabled() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mLowPowerStandbyController.isEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyEnabled(boolean enabled) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "setLowPowerStandbyEnabled");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.setEnabled(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "setLowPowerStandbyActiveDuringMaintenance");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.setActiveDuringMaintenance(activeDuringMaintenance);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ android.Manifest.permission.DEVICE_POWER
+ })
+ public void forceLowPowerStandbyActive(boolean active) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+ "forceLowPowerStandbyActive");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mLowPowerStandbyController.forceActive(active);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Gets the reason for the last time the phone had to reboot.
*
@@ -6249,6 +6498,16 @@
}
@Override
+ public void setLowPowerStandbyAllowlist(int[] appids) {
+ setLowPowerStandbyAllowlistInternal(appids);
+ }
+
+ @Override
+ public void setLowPowerStandbyActive(boolean enabled) {
+ setLowPowerStandbyActiveInternal(enabled);
+ }
+
+ @Override
public void startUidChanges() {
startUidChangesInternal();
}
@@ -6324,4 +6583,15 @@
}
};
+ static boolean isSameCallback(IWakeLockCallback callback1,
+ IWakeLockCallback callback2) {
+ if (callback1 == callback2) {
+ return true;
+ }
+ if (callback1 != null && callback2 != null
+ && callback1.asBinder() == callback2.asBinder()) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index 88c9850..d20c7f1 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -106,6 +106,7 @@
*/
private static final int FLAG_ON_AFTER_RELEASE = 0x8;
private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10;
+ private static final int FLAG_SYSTEM_WAKELOCK = 0x20;
private static final int MASK_LOWER_6_BITS = 0x3F;
private static final int MASK_LOWER_7_BITS = 0x7F;
@@ -296,6 +297,9 @@
if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) {
newFlags |= FLAG_ON_AFTER_RELEASE;
}
+ if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) {
+ newFlags |= FLAG_SYSTEM_WAKELOCK;
+ }
return newFlags;
}
@@ -455,6 +459,9 @@
if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) {
sb.append(",acq-causes-wake");
}
+ if ((flags & FLAG_SYSTEM_WAKELOCK) == FLAG_SYSTEM_WAKELOCK) {
+ sb.append(",system-wakelock");
+ }
}
}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
new file mode 100644
index 0000000..cc27546
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.resources;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.IResourcesManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+import com.android.server.am.ActivityManagerService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A service for managing information about ResourcesManagers
+ */
+public class ResourcesManagerService extends SystemService {
+ private ActivityManagerService mActivityManagerService;
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public ResourcesManagerService(@NonNull Context context) {
+ super(context);
+ publishBinderService(Context.RESOURCES_SERVICE, mService);
+ }
+
+ @Override
+ public void onStart() {
+ // Intentionally left empty.
+ }
+
+ private final IBinder mService = new IResourcesManager.Stub() {
+ @Override
+ public boolean dumpResources(String process, ParcelFileDescriptor fd,
+ RemoteCallback callback) throws RemoteException {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+ callback.sendResult(null);
+ throw new SecurityException("dump should only be called by shell");
+ }
+ return mActivityManagerService.dumpResources(process, fd, callback);
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd,
+ @NonNull PrintWriter pw, @Nullable String[] args) {
+ try {
+ mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw);
+ } catch (Exception e) {
+ pw.println("Exception while trying to dump all resources: " + e.getMessage());
+ e.printStackTrace(pw);
+ }
+ }
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out,
+ @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return (new ResourcesManagerShellCommand(this)).exec(
+ this,
+ in.getFileDescriptor(),
+ out.getFileDescriptor(),
+ err.getFileDescriptor(),
+ args);
+ }
+ };
+
+ public void setActivityManagerService(
+ ActivityManagerService activityManagerService) {
+ mActivityManagerService = activityManagerService;
+ }
+}
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
new file mode 100644
index 0000000..7d8336a
--- /dev/null
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.resources;
+
+import android.content.res.IResourcesManager;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for resources related commands
+ */
+public class ResourcesManagerShellCommand extends ShellCommand {
+ private static final String TAG = "ResourcesManagerShellCommand";
+
+ private final IResourcesManager mInterface;
+
+ public ResourcesManagerShellCommand(IResourcesManager anInterface) {
+ mInterface = anInterface;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter err = getErrPrintWriter();
+ try {
+ switch (cmd) {
+ case "dump":
+ return dumpResources();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (IllegalArgumentException e) {
+ err.println("Error: " + e.getMessage());
+ } catch (RemoteException e) {
+ err.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int dumpResources() throws RemoteException {
+ String processId = getNextArgRequired();
+ try {
+ ConditionVariable lock = new ConditionVariable();
+ RemoteCallback
+ finishCallback = new RemoteCallback(result -> lock.open(), null);
+
+ if (!mInterface.dumpResources(processId,
+ ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) {
+ getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId);
+ return -1;
+ }
+ lock.block(5000);
+ return 0;
+ } catch (IOException e) {
+ Slog.e(TAG, "Exception while dumping resources", e);
+ getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage());
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter out = getOutPrintWriter();
+ out.println("Resources manager commands:");
+ out.println(" help");
+ out.println(" Print this help text.");
+ out.println(" dump <PROCESS>");
+ out.println(" Dump the Resources objects in use as well as the history of Resources");
+
+ }
+}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index f519ced..243efb5 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.security;
+import static android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
import android.content.Context;
@@ -76,10 +78,24 @@
private void verifyAttestationForAllVerifiers(
AttestationProfile profile, int localBindingType, Bundle requirements,
byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) {
- // TODO(b/201696614): Implement
IVerificationResult result = new IVerificationResult();
- result.resultCode = RESULT_UNKNOWN;
+ // TODO(b/201696614): Implement
result.token = null;
+ switch (profile.getAttestationProfileId()) {
+ case PROFILE_SELF_TRUSTED:
+ Slog.d(TAG, "Verifying Self trusted profile.");
+ try {
+ result.resultCode =
+ AttestationVerificationSelfTrustedVerifierForTesting.getInstance()
+ .verifyAttestation(localBindingType, requirements, attestation);
+ } catch (Throwable t) {
+ result.resultCode = RESULT_FAILURE;
+ }
+ break;
+ default:
+ Slog.d(TAG, "No profile found, defaulting.");
+ result.resultCode = RESULT_UNKNOWN;
+ }
resultCallback.complete(result);
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
new file mode 100644
index 0000000..58df2bd
--- /dev/null
+++ b/services/core/java/com/android/server/security/AttestationVerificationSelfTrustedVerifierForTesting.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security;
+
+import static android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
+import static android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS;
+import static android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.ASN1OctetString;
+import com.android.internal.org.bouncycastle.asn1.ASN1Sequence;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verifies {@code PROFILE_SELF_TRUSTED} attestations.
+ *
+ * Verifies that the attesting environment can create an attestation with the same root certificate
+ * as the verifying device with a matching attestation challenge. Skips CRL revocations checking
+ * so this verifier can work in a hermetic test environment.
+ *
+ * This verifier profile is intended to be used only for testing.
+ */
+class AttestationVerificationSelfTrustedVerifierForTesting {
+ private static final String TAG = "AVF";
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
+
+ // The OID for the extension Android Keymint puts into device-generated certificates.
+ private static final String ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID =
+ "1.3.6.1.4.1.11129.2.1.17";
+
+ // ASN.1 sequence index values for the Android Keymint extension.
+ private static final int ATTESTATION_CHALLENGE_INDEX = 4;
+
+ private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+ private static final String GOLDEN_ALIAS =
+ AttestationVerificationSelfTrustedVerifierForTesting.class.getCanonicalName()
+ + ".Golden";
+
+ private static volatile AttestationVerificationSelfTrustedVerifierForTesting
+ sAttestationVerificationSelfTrustedVerifier = null;
+
+ private final CertificateFactory mCertificateFactory;
+ private final CertPathValidator mCertPathValidator;
+ private final KeyStore mAndroidKeyStore;
+ private X509Certificate mGoldenRootCert;
+
+ static AttestationVerificationSelfTrustedVerifierForTesting getInstance()
+ throws Exception {
+ if (sAttestationVerificationSelfTrustedVerifier == null) {
+ synchronized (AttestationVerificationSelfTrustedVerifierForTesting.class) {
+ if (sAttestationVerificationSelfTrustedVerifier == null) {
+ sAttestationVerificationSelfTrustedVerifier =
+ new AttestationVerificationSelfTrustedVerifierForTesting();
+ }
+ }
+ }
+ return sAttestationVerificationSelfTrustedVerifier;
+ }
+
+ private static void debugVerboseLog(String str, Throwable t) {
+ if (DEBUG) {
+ Slog.v(TAG, str, t);
+ }
+ }
+
+ private static void debugVerboseLog(String str) {
+ if (DEBUG) {
+ Slog.v(TAG, str);
+ }
+ }
+
+ private AttestationVerificationSelfTrustedVerifierForTesting() throws Exception {
+ mCertificateFactory = CertificateFactory.getInstance("X.509");
+ mCertPathValidator = CertPathValidator.getInstance("PKIX");
+ mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
+ mAndroidKeyStore.load(null);
+ if (!mAndroidKeyStore.containsAlias(GOLDEN_ALIAS)) {
+ KeyPairGenerator kpg =
+ KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE);
+ KeyGenParameterSpec parameterSpec = new KeyGenParameterSpec.Builder(
+ GOLDEN_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setAttestationChallenge(GOLDEN_ALIAS.getBytes())
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512).build();
+ kpg.initialize(parameterSpec);
+ kpg.generateKeyPair();
+ }
+
+ X509Certificate[] goldenCerts = (X509Certificate[])
+ ((KeyStore.PrivateKeyEntry) mAndroidKeyStore.getEntry(GOLDEN_ALIAS, null))
+ .getCertificateChain();
+ mGoldenRootCert = goldenCerts[goldenCerts.length - 1];
+ }
+
+ int verifyAttestation(
+ int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) {
+ List<X509Certificate> certificates = new ArrayList<>();
+ ByteArrayInputStream bis = new ByteArrayInputStream(attestation);
+ try {
+ while (bis.available() > 0) {
+ certificates.add((X509Certificate) mCertificateFactory.generateCertificate(bis));
+ }
+ } catch (CertificateException e) {
+ debugVerboseLog("Unable to parse certificates from attestation", e);
+ return RESULT_FAILURE;
+ }
+
+ if (localBindingType == TYPE_CHALLENGE
+ && validateRequirements(requirements)
+ && checkLeafChallenge(requirements, certificates)
+ && verifyCertificateChain(certificates)) {
+ return RESULT_SUCCESS;
+ }
+
+ return RESULT_FAILURE;
+ }
+
+ private boolean verifyCertificateChain(List<X509Certificate> certificates) {
+ if (certificates.size() < 2) {
+ debugVerboseLog("Certificate chain less than 2 in size.");
+ return false;
+ }
+
+ try {
+ CertPath certificatePath = mCertificateFactory.generateCertPath(certificates);
+ PKIXParameters validationParams = new PKIXParameters(getTrustAnchors());
+ // Skipping revocation checking because we want this to work in a hermetic test
+ // environment.
+ validationParams.setRevocationEnabled(false);
+ mCertPathValidator.validate(certificatePath, validationParams);
+ } catch (Throwable t) {
+ debugVerboseLog("Invalid certificate chain", t);
+ return false;
+ }
+
+ return true;
+ }
+
+ private Set<TrustAnchor> getTrustAnchors() {
+ return Collections.singleton(new TrustAnchor(mGoldenRootCert, null));
+ }
+
+ private boolean validateRequirements(Bundle requirements) {
+ if (requirements.size() != 1) {
+ debugVerboseLog("Requirements does not contain exactly 1 key.");
+ return false;
+ }
+ if (!requirements.containsKey(PARAM_CHALLENGE)) {
+ debugVerboseLog("Requirements does not contain key: " + PARAM_CHALLENGE);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkLeafChallenge(Bundle requirements, List<X509Certificate> certificates) {
+ // Verify challenge
+ byte[] challenge;
+ try {
+ challenge = getChallengeFromCert(certificates.get(0));
+ } catch (Throwable t) {
+ debugVerboseLog("Unable to parse challenge from certificate.", t);
+ return false;
+ }
+
+ if (Arrays.equals(requirements.getByteArray(PARAM_CHALLENGE), challenge)) {
+ return true;
+ } else {
+ debugVerboseLog("Self-Trusted validation failed; challenge mismatch.");
+ return false;
+ }
+ }
+
+ private byte[] getChallengeFromCert(@NonNull X509Certificate x509Certificate)
+ throws CertificateEncodingException, IOException {
+ Certificate certificate = Certificate.getInstance(
+ new ASN1InputStream(x509Certificate.getEncoded()).readObject());
+ ASN1Sequence keyAttributes = (ASN1Sequence) certificate.getTBSCertificate().getExtensions()
+ .getExtensionParsedValue(
+ new ASN1ObjectIdentifier(ANDROID_KEYMINT_KEY_DESCRIPTION_EXTENSION_OID));
+ return ((ASN1OctetString) keyAttributes.getObjectAt(ATTESTATION_CHALLENGE_INDEX))
+ .getOctets();
+ }
+}
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..94f483c 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;
@@ -46,11 +47,13 @@
import android.graphics.drawable.Icon;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
+import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.media.MediaRoute2Info;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -84,10 +87,13 @@
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.IUndoMediaTransferCallback;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.internal.statusbar.StatusBarIcon;
@@ -145,6 +151,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManager;
private final PackageManagerInternal mPackageManagerInternal;
+ private final SessionMonitor mSessionMonitor;
private int mCurrentUserId;
private boolean mTracingEnabled;
@@ -153,6 +160,8 @@
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
private IUdfpsHbmListener mUdfpsHbmListener;
+ @GuardedBy("mLock")
+ private IBiometricContextListener mBiometricContextListener;
@GuardedBy("mCurrentRequestAddTilePackages")
private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
@@ -260,6 +269,7 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mTileRequestTracker = new TileRequestTracker(mContext);
+ mSessionMonitor = new SessionMonitor(mContext);
}
private IOverlayManager getOverlayManager() {
@@ -889,6 +899,20 @@
}
@Override
+ public void setBiometicContextListener(IBiometricContextListener listener) {
+ enforceStatusBarService();
+ synchronized (mLock) {
+ mBiometricContextListener = listener;
+ }
+ if (mBar != null) {
+ try {
+ mBar.setBiometicContextListener(listener);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
enforceStatusBarService();
if (mBar != null) {
@@ -1243,6 +1267,12 @@
"StatusBarManagerService");
}
+ private void enforceMediaContentControl() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL,
+ "StatusBarManagerService");
+ }
+
/**
* For targetSdk S+ we require STATUS_BAR. For targetSdk < S, we only require EXPAND_STATUS_BAR
* but also require that it falls into one of the allowed use-cases to lock down abuse vector.
@@ -1310,6 +1340,7 @@
mHandler.post(() -> {
synchronized (mLock) {
setUdfpsHbmListener(mUdfpsHbmListener);
+ setBiometicContextListener(mBiometricContextListener);
}
});
}
@@ -1870,6 +1901,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);
}
@@ -1942,6 +1995,53 @@
return false;
}
+ /**
+ * Notifies the system of a new media tap-to-transfer state for the *sender* device. See
+ * {@link StatusBarManager.updateMediaTapToTransferSenderDisplay} for more information.
+ *
+ * @param undoCallback a callback that will be triggered if the user elects to undo a media
+ * transfer.
+ *
+ * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
+ * permission.
+ */
+ @Override
+ public void updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState int displayState,
+ @NonNull MediaRoute2Info routeInfo,
+ @Nullable IUndoMediaTransferCallback undoCallback
+ ) {
+ enforceMediaContentControl();
+ if (mBar != null) {
+ try {
+ mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e);
+ }
+ }
+ }
+
+ /**
+ * Notifies the system of a new media tap-to-transfer state for the *receiver* device. See
+ * {@link StatusBarManager.updateMediaTapToTransferReceiverDisplay} for more information.
+ *
+ * Requires the caller to have the {@link android.Manifest.permission.MEDIA_CONTENT_CONTROL}
+ * permission.
+ */
+ @Override
+ public void updateMediaTapToTransferReceiverDisplay(
+ @StatusBarManager.MediaTransferReceiverState int displayState,
+ MediaRoute2Info routeInfo) {
+ enforceMediaContentControl();
+ if (mBar != null) {
+ try {
+ mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
+ }
+ }
+ }
+
/** @hide */
public void passThroughShellCommand(String[] args, FileDescriptor fd) {
enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index ff2f08b..27c0bee 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -15,34 +15,56 @@
*/
package com.android.server.tracing;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.os.IMessenger;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
+import android.service.tracing.TraceReportService;
import android.tracing.ITracingServiceProxy;
+import android.tracing.TraceReportParams;
import android.util.Log;
+import android.util.LruCache;
+import android.util.Slog;
+import com.android.internal.infra.ServiceConnector;
import com.android.server.SystemService;
+import java.io.IOException;
+
/**
* TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the
- * system tracing app Traceur.
+ * other components (e.g. system tracing app Traceur, trace reporting apps).
*
* Access to this service is restricted via SELinux. Normal apps do not have access.
*
* @hide
*/
public class TracingServiceProxy extends SystemService {
- private static final String TAG = "TracingServiceProxy";
-
public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy";
-
+ private static final String TAG = "TracingServiceProxy";
private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur";
private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService";
+ private static final int MAX_CACHED_REPORTER_SERVICES = 8;
+
+ // The maximum size of the trace allowed if the option |usePipeForTesting| is set.
+ // Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can
+ // write to the pipe without blocking) to avoid system_server blocking on this.
+ // (on Linux, the minimum value is 4K i.e. 1 minimally sized page).
+ private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024;
+
// Keep this in sync with the definitions in TraceService
private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
"com.android.traceur.NOTIFY_SESSION_STOPPED";
@@ -51,16 +73,22 @@
private final Context mContext;
private final PackageManager mPackageManager;
+ private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
/**
- * Notifies system tracing app that a tracing session has ended. If a session is repurposed
- * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
- * there is no buffer available to dump.
- */
+ * Notifies system tracing app that a tracing session has ended. If a session is repurposed
+ * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
+ * there is no buffer available to dump.
+ */
@Override
public void notifyTraceSessionEnded(boolean sessionStolen) {
- notifyTraceur(sessionStolen);
+ TracingServiceProxy.this.notifyTraceur(sessionStolen);
+ }
+
+ @Override
+ public void reportTrace(@NonNull TraceReportParams params) {
+ TracingServiceProxy.this.reportTrace(params);
}
};
@@ -68,6 +96,7 @@
super(context);
mContext = context;
mPackageManager = context.getPackageManager();
+ mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES);
}
@Override
@@ -103,4 +132,119 @@
Log.e(TAG, "Failed to locate Traceur", e);
}
}
+
+ private void reportTrace(@NonNull TraceReportParams params) {
+ // We don't need to do any permission checks on the caller because access
+ // to this service is guarded by SELinux.
+ ComponentName component = new ComponentName(params.reporterPackageName,
+ params.reporterClassName);
+ if (!hasBindServicePermission(component)) {
+ return;
+ }
+ boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
+ boolean hasUsageStatsPermission = hasPermission(component,
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ if (!hasDumpPermission || !hasUsageStatsPermission) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ reportTrace(getOrCreateReporterService(component), params);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void reportTrace(
+ @NonNull ServiceConnector<IMessenger> reporterService,
+ @NonNull TraceReportParams params) {
+ reporterService.post(messenger -> {
+ if (params.usePipeForTesting) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) {
+ try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) {
+ byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE);
+ if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) {
+ throw new IllegalArgumentException(
+ "Trace file too large when |usePipeForTesting| is set.");
+ }
+ o.write(array);
+ }
+ }
+ params.fd = pipe[0];
+ }
+
+ Message message = Message.obtain();
+ message.what = TraceReportService.MSG_REPORT_TRACE;
+ message.obj = params;
+ messenger.send(message);
+ }).whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Failed to report trace", err);
+ }
+ try {
+ params.fd.close();
+ } catch (IOException ignored) {
+ }
+ });
+ }
+
+ private ServiceConnector<IMessenger> getOrCreateReporterService(
+ @NonNull ComponentName component) {
+ ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component);
+ if (connector == null) {
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ connector = new ServiceConnector.Impl<IMessenger>(
+ mContext, intent,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
+ mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) {
+ private static final long DISCONNECT_TIMEOUT_MS = 15_000;
+ private static final long REQUEST_TIMEOUT_MS = 10_000;
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ return DISCONNECT_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MS;
+ }
+ };
+ mCachedReporterServices.put(intent.getComponent(), connector);
+ }
+ return connector;
+ }
+
+ private boolean hasPermission(@NonNull ComponentName componentName,
+ @NonNull String permission) throws SecurityException {
+ if (mPackageManager.checkPermission(permission, componentName.getPackageName())
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString() + " does not have "
+ + permission + " permission");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean hasBindServicePermission(@NonNull ComponentName componentName) {
+ ServiceInfo info;
+ try {
+ info = mPackageManager.getServiceInfo(componentName, 0);
+ } catch (NameNotFoundException ex) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString() + " does not exist");
+ return false;
+ }
+ if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) {
+ Slog.e(TAG,
+ "Trace reporting service " + componentName.toShortString()
+ + " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE
+ + " permission; instead requests " + info.permission);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 06ce4a4..1dea3d7 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -71,6 +71,7 @@
private static final int MSG_ESCROW_TOKEN_STATE = 9;
private static final int MSG_UNLOCK_USER = 10;
private static final int MSG_SHOW_KEYGUARD_ERROR_MESSAGE = 11;
+ private static final int MSG_LOCK_USER = 12;
/**
* Time in uptime millis that we wait for the service connection, both when starting
@@ -100,6 +101,8 @@
// Trust state
private boolean mTrusted;
+ private boolean mWaitingForTrustableDowngrade = false;
+ private boolean mTrustable;
private CharSequence mMessage;
private boolean mDisplayTrustGrantedMessage;
private boolean mTrustDisabledByDpm;
@@ -108,6 +111,25 @@
private AlarmManager mAlarmManager;
private final Intent mAlarmIntent;
private PendingIntent mAlarmPendingIntent;
+ private final BroadcastReceiver mTrustableDowngradeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
+ return;
+ }
+ if (!mWaitingForTrustableDowngrade) {
+ return;
+ }
+ // are these the broadcasts we want to listen to
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
+ || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
+ mTrusted = false;
+ mTrustable = true;
+ mWaitingForTrustableDowngrade = false;
+ mTrustManagerService.updateTrust(mUserId, 0);
+ }
+ }
+ };
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -126,16 +148,21 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_GRANT_TRUST:
- // TODO(b/213631675): Respect FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
if (!isConnected()) {
Log.w(TAG, "Agent is not connected, cannot grant trust: "
+ mName.flattenToShortString());
return;
}
mTrusted = true;
+ mTrustable = false;
mMessage = (CharSequence) msg.obj;
int flags = msg.arg1;
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+ if ((flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
+ mWaitingForTrustableDowngrade = true;
+ } else {
+ mWaitingForTrustableDowngrade = false;
+ }
long durationMs = msg.getData().getLong(DATA_DURATION);
if (durationMs > 0) {
final long duration;
@@ -270,6 +297,13 @@
mTrustManagerService.showKeyguardErrorMessage(message);
break;
}
+ case MSG_LOCK_USER: {
+ mTrusted = false;
+ mTrustable = false;
+ mTrustManagerService.updateTrust(mUserId, 0 /* flags */);
+ mTrustManagerService.lockUser(mUserId);
+ break;
+ }
}
}
};
@@ -295,6 +329,11 @@
}
@Override
+ public void lockUser() {
+ mHandler.sendEmptyMessage(MSG_LOCK_USER);
+ }
+
+ @Override
public void setManagingTrust(boolean managingTrust) {
if (DEBUG) Slog.d(TAG, "managingTrust()");
mHandler.obtainMessage(MSG_MANAGING_TRUST, managingTrust ? 1 : 0, 0).sendToTarget();
@@ -427,6 +466,9 @@
final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
+ IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
// Schedules a restart for when connecting times out. If the connection succeeds,
// the restart is canceled in mCallback's onConnected.
scheduleRestart();
@@ -435,6 +477,7 @@
if (mBound) {
mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null,
Context.RECEIVER_EXPORTED);
+ mContext.registerReceiver(mTrustableDowngradeReceiver, trustableFilter);
} else {
Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString());
}
@@ -591,6 +634,10 @@
return mTrusted && mManagingTrust && !mTrustDisabledByDpm;
}
+ public boolean isTrustable() {
+ return mTrustable && mManagingTrust && !mTrustDisabledByDpm;
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9bed24d..6aafd4a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -54,6 +54,7 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -65,6 +66,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.IWindowManager;
@@ -145,6 +147,21 @@
@GuardedBy("mUserIsTrusted")
private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray();
+ //TODO(b/215724686): remove flag
+ public static final boolean ENABLE_ACTIVE_UNLOCK_FLAG = SystemProperties.getBoolean(
+ "fw.enable_active_unlock_flag", true);
+
+ private enum TrustState {
+ UNTRUSTED, // the phone is not unlocked by any trustagents
+ TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
+ // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
+ TRUSTED // the phone is unlocked
+ };
+
+ @GuardedBy("mUserTrustState")
+ private final SparseArray<TrustManagerService.TrustState> mUserTrustState =
+ new SparseArray<>();
+
/**
* Stores the locked state for users on the device. There are three different type of users
* which are handled slightly differently:
@@ -228,7 +245,6 @@
}
// Extend unlock config and logic
-
private final class SettingsObserver extends ContentObserver {
private final Uri TRUST_AGENTS_EXTEND_UNLOCK =
Settings.Secure.getUriFor(Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK);
@@ -396,6 +412,14 @@
}
private void updateTrust(int userId, int flags, boolean isFromUnlock) {
+ if (ENABLE_ACTIVE_UNLOCK_FLAG) {
+ updateTrustWithRenewableUnlock(userId, flags, isFromUnlock);
+ } else {
+ updateTrustWithExtendUnlock(userId, flags, isFromUnlock);
+ }
+ }
+
+ private void updateTrustWithExtendUnlock(int userId, int flags, boolean isFromUnlock) {
boolean managed = aggregateIsTrustManaged(userId);
dispatchOnTrustManagedChanged(managed, userId);
if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -441,6 +465,65 @@
}
}
+ private void updateTrustWithRenewableUnlock(int userId, int flags, boolean isFromUnlock) {
+ boolean managed = aggregateIsTrustManaged(userId);
+ dispatchOnTrustManagedChanged(managed, userId);
+ if (mStrongAuthTracker.isTrustAllowedForUser(userId)
+ && isTrustUsuallyManagedInternal(userId) != managed) {
+ updateTrustUsuallyManaged(userId, managed);
+ }
+
+ boolean trustedByAtLeastOneAgent = aggregateIsTrusted(userId);
+ boolean trustableByAtLeastOneAgent = aggregateIsTrustable(userId);
+ boolean wasTrusted;
+ boolean wasTrustable;
+ TrustState pendingTrustState;
+
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ boolean alreadyUnlocked = false;
+ try {
+ alreadyUnlocked = !wm.isKeyguardLocked();
+ } catch (RemoteException e) {
+ }
+
+ synchronized (mUserTrustState) {
+ wasTrusted = (mUserTrustState.get(userId) == TrustState.TRUSTED);
+ wasTrustable = (mUserTrustState.get(userId) == TrustState.TRUSTABLE);
+ boolean renewingTrust = wasTrustable && (
+ (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
+ boolean canMoveToTrusted = alreadyUnlocked || isFromUnlock || renewingTrust;
+ boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
+
+ if (trustedByAtLeastOneAgent && wasTrusted) {
+ // no change
+ return;
+ } else if (trustedByAtLeastOneAgent && canMoveToTrusted
+ && upgradingTrustForCurrentUser) {
+ pendingTrustState = TrustState.TRUSTED;
+ } else if (trustableByAtLeastOneAgent && (wasTrusted || wasTrustable)
+ && upgradingTrustForCurrentUser) {
+ pendingTrustState = TrustState.TRUSTABLE;
+ } else {
+ pendingTrustState = TrustState.UNTRUSTED;
+ }
+
+ mUserTrustState.put(userId, pendingTrustState);
+ }
+ if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
+
+ boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
+ dispatchOnTrustChanged(isNowTrusted, userId, flags, getTrustGrantedMessages(userId));
+ if (isNowTrusted != wasTrusted) {
+ refreshDeviceLockedForUser(userId);
+ if (!isNowTrusted) {
+ maybeLockScreen(userId);
+ } else {
+ scheduleTrustTimeout(userId, false /* override */);
+ }
+ }
+ }
+
+
private void updateTrustUsuallyManaged(int userId, boolean managed) {
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.put(userId, managed);
@@ -472,6 +555,20 @@
mLockPatternUtils.unlockUserWithToken(handle, token, userId);
}
+ /**
+ * Locks the phone and requires some auth (not trust) like a biometric or passcode before
+ * unlocking.
+ */
+ public void lockUser(int userId) {
+ mLockPatternUtils.requireStrongAuth(
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error locking screen when called from trust agent");
+ }
+ }
+
void showKeyguardErrorMessage(CharSequence message) {
dispatchOnTrustError(message);
}
@@ -950,6 +1047,21 @@
return false;
}
+ private boolean aggregateIsTrustable(int userId) {
+ if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+ return false;
+ }
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == userId) {
+ if (info.agent.isTrustable()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private List<String> getTrustGrantedMessages(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7164c6c..ff96aeb 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -43,6 +43,7 @@
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
@@ -1081,7 +1082,7 @@
@Override
public void overridePendingTransition(IBinder token, String packageName,
- int enterAnim, int exitAnim) {
+ int enterAnim, int exitAnim, @ColorInt int backgroundColor) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
@@ -1091,7 +1092,7 @@
r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
- enterAnim, exitAnim, r.mOverrideTaskTransition),
+ enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
null /* startCallback */, null /* finishCallback */);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 897549b..6836e31 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,6 +46,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
+import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -241,6 +244,7 @@
import android.app.TaskInfo.CameraCompatControlState;
import android.app.WaitResult;
import android.app.WindowConfiguration;
+import android.app.admin.DevicePolicyManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
@@ -272,6 +276,7 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.net.Uri;
import android.os.Binder;
@@ -538,6 +543,15 @@
// Tracking splash screen status from previous activity
boolean mSplashScreenStyleEmpty = false;
+ Drawable mEnterpriseThumbnailDrawable;
+
+ private void updateEnterpriseThumbnailDrawable(Context context) {
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ mEnterpriseThumbnailDrawable = dpm.getDrawable(
+ WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION,
+ () -> context.getDrawable(R.drawable.ic_corp_badge));
+ }
+
static final int LAUNCH_SOURCE_TYPE_SYSTEM = 1;
static final int LAUNCH_SOURCE_TYPE_HOME = 2;
static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
@@ -716,6 +730,11 @@
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+ // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+ // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+ private boolean mIsEligibleForFixedOrientationLetterbox;
+
// State of the Camera app compat control which is used to correct stretched viewfinder
// in apps that don't handle all possible configurations and changes between them correctly.
@CameraCompatControlState
@@ -1925,6 +1944,8 @@
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
mActivityRecordInputSink = new ActivityRecordInputSink(this);
+
+ updateEnterpriseThumbnailDrawable(mAtmService.mUiContext);
}
/**
@@ -4469,6 +4490,7 @@
pendingOptions.getOverrideTaskTransition());
options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+ pendingOptions.getCustomBackgroundColor(),
pendingOptions.getOverrideTaskTransition());
startCallback = pendingOptions.getAnimationStartedListener();
finishCallback = pendingOptions.getAnimationFinishedListener();
@@ -6568,7 +6590,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 +6601,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 +6631,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 +6662,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 +6670,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);
@@ -6922,7 +6950,7 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS,
+ final boolean show = isVisible() || isAnimating(PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
if (mSurfaceControl != null) {
@@ -6981,12 +7009,11 @@
return;
}
final Rect frame = win.getRelativeFrame();
- final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId
- ? R.drawable.ic_account_circle
- : R.drawable.ic_corp_badge;
- final HardwareBuffer thumbnail =
- getDisplayContent().mAppTransition
- .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+ final Drawable thumbnailDrawable = task.mUserId == mWmService.mCurrentUserId
+ ? mAtmService.mUiContext.getDrawable(R.drawable.ic_account_circle)
+ : mEnterpriseThumbnailDrawable;
+ final HardwareBuffer thumbnail = getDisplayContent().mAppTransition
+ .createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
if (thumbnail == null) {
return;
}
@@ -7620,6 +7647,26 @@
}
/**
+ * Whether this activity is eligible for letterbox eduction.
+ *
+ * <p>Conditions that need to be met:
+ *
+ * <ul>
+ * <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true.
+ * <li>The activity is eligible for fixed orientation letterbox.
+ * <li>The activity is in fullscreen.
+ * <li>The activity is portrait-only.
+ * </ul>
+ */
+ // TODO(b/215316431): Add tests
+ boolean isEligibleForLetterboxEducation() {
+ return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
+ && mIsEligibleForFixedOrientationLetterbox
+ && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ }
+
+ /**
* In some cases, applying insets to bounds changes the orientation. For example, if a
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
* insets such as the status and nav bars are applied, the activity may actually have a
@@ -7681,6 +7728,7 @@
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
int windowingMode) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect stableBounds = new Rect();
// If orientation is respected when insets are applied, then stableBounds will be empty.
@@ -7720,8 +7768,11 @@
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
- if (forcedOrientation == ORIENTATION_UNDEFINED
- || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
+ mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != parentOrientation;
+
+ if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+ || orientationRespectedWithInsets)) {
return;
}
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..ca4d717 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -24,6 +24,7 @@
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.Manifest.permission.MANAGE_GAME_ACTIVITY;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REMOVE_TASKS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -1771,6 +1772,43 @@
}
@Override
+ public int startActivityFromGameSession(IApplicationThread caller, String callingPackage,
+ String callingFeatureId, int callingPid, int callingUid, Intent intent, int taskId,
+ int userId) {
+ if (checkCallingPermission(MANAGE_GAME_ACTIVITY) != PERMISSION_GRANTED) {
+ final String msg = "Permission Denial: startActivityFromGameSession() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + MANAGE_GAME_ACTIVITY;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ assertPackageMatchesCallingUid(callingPackage);
+
+ final ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchTaskId(taskId);
+
+ userId = handleIncomingUser(callingPid, callingUid, userId, "startActivityFromGameSession");
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return getActivityStartController()
+ .obtainStarter(intent, "startActivityFromGameSession")
+ .setCaller(caller)
+ .setCallingUid(callingUid)
+ .setCallingPid(callingPid)
+ .setCallingPackage(intent.getPackage())
+ .setCallingFeatureId(callingFeatureId)
+ .setUserId(userId)
+ .setActivityOptions(activityOptions.toBundle())
+ .setRealCallingUid(Binder.getCallingUid())
+ .execute();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public BackNavigationInfo startBackNavigation() {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
@@ -3959,6 +3997,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 +6667,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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d5abe4f..56adcfd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -93,7 +93,6 @@
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
-import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -101,6 +100,7 @@
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Debug;
@@ -539,8 +539,8 @@
* animation.
*/
HardwareBuffer createCrossProfileAppsThumbnail(
- @DrawableRes int thumbnailDrawableRes, Rect frame) {
- return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
+ Drawable thumbnailDrawable, Rect frame) {
+ return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
}
Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index a8779fa..45a6cb9 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -29,6 +29,7 @@
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.BackNavigationInfo;
+import android.window.IOnBackInvokedCallback;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -91,29 +92,41 @@
HardwareBuffer screenshotBuffer = null;
int prevTaskId;
int prevUserId;
+ IOnBackInvokedCallback callback;
synchronized (task.mWmService.mGlobalLock) {
activityRecord = task.topRunningActivity();
removedWindowContainer = activityRecord;
taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, topRunningActivity=%s",
- task, activityRecord);
+ WindowState topChild = activityRecord.getTopChild();
+ callback = topChild.getOnBackInvokedCallback();
- // IME is visible, back gesture will dismiss it, nothing to preview.
- if (task.getDisplayContent().getImeContainer().isVisible()) {
- return null;
- }
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, "
+ + "topRunningActivity=%s, topWindow=%s backCallback=%s",
+ task, activityRecord, topChild,
+ callback != null ? callback.getClass().getSimpleName() : null);
- // Current Activity is home, there is no previous activity to display
- if (activityRecord.isActivityTypeHome()) {
- return null;
+ // For IME and Home, either a callback is registered, or we do nothing. In both cases,
+ // we don't need to pass the leashes below.
+ if (task.getDisplayContent().getImeContainer().isVisible()
+ || activityRecord.isActivityTypeHome()) {
+ if (callback != null) {
+ return new BackNavigationInfo(BackNavigationInfo.TYPE_CALLBACK,
+ null /* topWindowLeash */, null /* screenshotSurface */,
+ null /* screenshotBuffer */, null /* taskWindowConfiguration */,
+ null /* onBackNavigationDone */, callback /* onBackInvokedCallback */);
+ } else {
+ return null;
+ }
}
prev = task.getActivity(
(r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity());
- if (prev != null) {
+ if (callback != null) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ } else if (prev != null) {
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
} else if (task.returnsToHomeRootTask()) {
prevTask = null;
@@ -148,7 +161,7 @@
// Prepare a leash to animate the current top window
animLeash = removedWindowContainer.makeAnimationLeash()
- .setName("BackPreview Leash")
+ .setName("BackPreview Leash for " + removedWindowContainer)
.setHidden(false)
.setBLASTLayer()
.build();
@@ -158,7 +171,7 @@
}
SurfaceControl.Builder builder = new SurfaceControl.Builder()
- .setName("BackPreview Screenshot")
+ .setName("BackPreview Screenshot for " + prev)
.setParent(animationLeashParent)
.setHidden(false)
.setBLASTLayer();
@@ -171,6 +184,10 @@
screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
}
}
+
+ // The Animation leash needs to be above the screenshot surface, but the animation leash
+ // needs to be added before to be in the synchronized block.
+ tx.setLayer(animLeash, 1);
tx.apply();
WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
@@ -183,13 +200,16 @@
return null;
}
+ RemoteCallback onBackNavigationDone = new RemoteCallback(
+ result -> resetSurfaces(finalRemovedWindowContainer
+ ));
return new BackNavigationInfo(backType,
animLeash,
screenshotSurface,
screenshotBuffer,
taskWindowConfiguration,
- new RemoteCallback(result -> resetSurfaces(finalRemovedWindowContainer
- )));
+ onBackNavigationDone,
+ callback);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a1c823e..c87027d 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);
}
/**
@@ -5490,26 +5486,46 @@
}
void updateKeepClearAreas() {
+ final List<Rect> restrictedKeepClearAreas = new ArrayList();
+ final List<Rect> unrestrictedKeepClearAreas = new ArrayList();
+ getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas);
mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged(
- this, getKeepClearAreas());
+ this, restrictedKeepClearAreas, unrestrictedKeepClearAreas);
}
/**
- * Returns all keep-clear areas from visible windows on this display.
+ * Fills {@param outRestricted} with all keep-clear areas from visible, relevant windows
+ * on this display, which set restricted keep-clear areas.
+ * Fills {@param outUnrestricted} with keep-clear areas from visible, relevant windows on this
+ * display, which set unrestricted keep-clear areas.
+ *
+ * For context on restricted vs unrestricted keep-clear areas, see
+ * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}.
*/
- ArrayList<Rect> getKeepClearAreas() {
- final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+ void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
forAllWindows(w -> {
if (w.isVisible() && !w.inPinnedWindowingMode()) {
- keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ if (w.mSession.mSetsUnrestrictedKeepClearAreas) {
+ outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ } else {
+ outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+ }
}
// We stop traversing when we reach the base of a fullscreen app.
return w.getWindowType() == TYPE_BASE_APPLICATION
&& w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
}, true);
+ }
+
+ /**
+ * Returns all keep-clear areas from visible, relevant windows on this display.
+ */
+ ArrayList<Rect> getKeepClearAreas() {
+ final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+ getKeepClearAreas(keepClearAreas, keepClearAreas);
return keepClearAreas;
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 276dbe9..e18d539 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -120,12 +120,13 @@
mDisplayListeners.finishBroadcast();
}
- void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) {
+ void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> restricted,
+ List<Rect> unrestricted) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged(
- display.mDisplayId, keepClearAreas);
+ display.mDisplayId, restricted, unrestricted);
} catch (RemoteException e) {
}
}
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/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f91969b..1f0fdcf 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -24,6 +24,7 @@
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.PointF;
import android.os.Debug;
import android.os.IBinder;
@@ -254,6 +255,25 @@
}
}
+ @Override
+ @Nullable
+ public SurfaceControl createSurfaceForGestureMonitor(String name, int displayId) {
+ synchronized (mService.mGlobalLock) {
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+ + " - DisplayContent not found.");
+ return null;
+ }
+ return mService.makeSurfaceBuilder(dc.getSession())
+ .setContainerLayer()
+ .setName(name)
+ .setCallsite("createSurfaceForGestureMonitor")
+ .setParent(dc.getSurfaceControl())
+ .build();
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 9326a2e..1e12173 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -32,6 +32,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -134,7 +135,7 @@
/** Updates the target which can control system bars. */
void updateBarControlTarget(@Nullable WindowState focusedWin) {
- if (mFocusedWin != focusedWin){
+ if (mFocusedWin != focusedWin) {
abortTransient();
}
mFocusedWin = focusedWin;
@@ -156,7 +157,7 @@
}
boolean isHidden(@InternalInsetsType int type) {
- final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
+ final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
}
@@ -181,6 +182,10 @@
mShowingTransientTypes.toArray(), isGestureOnSystemBar);
}
updateBarControlTarget(mFocusedWin);
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR),
+ isGestureOnSystemBar);
// The leashes can be created while updating bar control target. The surface transaction
// of the new leashes might not be applied yet. The callback posted here ensures we can
@@ -198,6 +203,12 @@
if (mShowingTransientTypes.size() == 0) {
return;
}
+
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
@@ -219,13 +230,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
*/
@@ -363,6 +384,11 @@
mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray());
}
mShowingTransientTypes.clear();
+
+ dispatchTransientSystemBarsVisibilityChanged(
+ mFocusedWin,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
}
private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
@@ -511,6 +537,32 @@
listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
}
+ private void dispatchTransientSystemBarsVisibilityChanged(
+ @Nullable WindowState focusedWindow,
+ boolean areVisible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ if (focusedWindow == null) {
+ return;
+ }
+
+ Task task = focusedWindow.getTask();
+ if (task == null) {
+ return;
+ }
+
+ int taskId = task.mTaskId;
+ boolean isValidTaskId = taskId != ActivityTaskManager.INVALID_TASK_ID;
+ if (!isValidTaskId) {
+ return;
+ }
+
+ mDisplayContent.mWmService.mTaskSystemBarsListenerController
+ .dispatchTransientSystemBarVisibilityChanged(
+ taskId,
+ areVisible,
+ wereRevealedFromSwipeOnSystemBar);
+ }
+
private class BarWindow {
private final int mId;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 1955e30..ad2767c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -126,6 +126,9 @@
@LetterboxReachabilityPosition
private volatile int mLetterboxPositionForReachability;
+ // Whether education is allowed for letterboxed fullscreen apps.
+ private boolean mIsEducationEnabled;
+
LetterboxConfiguration(Context systemUiContext) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -143,6 +146,8 @@
R.bool.config_letterboxIsReachabilityEnabled);
mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
mLetterboxPositionForReachability = mDefaultPositionForReachability;
+ mIsEducationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEducationEnabled);
}
/**
@@ -501,4 +506,27 @@
mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
}
+ /**
+ * Whether education is allowed for letterboxed fullscreen apps.
+ */
+ boolean getIsEducationEnabled() {
+ return mIsEducationEnabled;
+ }
+
+ /**
+ * Overrides whether education is allowed for letterboxed fullscreen apps.
+ */
+ void setIsEducationEnabled(boolean enabled) {
+ mIsEducationEnabled = enabled;
+ }
+
+ /**
+ * Resets whether education is allowed for letterboxed fullscreen apps to
+ * {@link R.bool.config_letterboxIsEducationEnabled}.
+ */
+ void resetIsEducationEnabled() {
+ mIsEducationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEducationEnabled);
+ }
+
}
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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index a049d65..d4a7a5d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -380,8 +380,11 @@
final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
if (cn != null) {
try {
- final ApplicationInfo appInfo = AppGlobals.getPackageManager()
- .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ cn.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS,
+ mService.mContext.getUserId());
if (appInfo != null) {
mRecentsUid = appInfo.uid;
mRecentsComponent = cn;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76a7981..ee03d02 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -981,6 +981,29 @@
mWmService.checkDrawnWindowsLocked();
}
+ final int N = mWmService.mPendingRemove.size();
+ if (N > 0) {
+ if (mWmService.mPendingRemoveTmp.length < N) {
+ mWmService.mPendingRemoveTmp = new WindowState[N + 10];
+ }
+ mWmService.mPendingRemove.toArray(mWmService.mPendingRemoveTmp);
+ mWmService.mPendingRemove.clear();
+ ArrayList<DisplayContent> displayList = new ArrayList();
+ for (i = 0; i < N; i++) {
+ final WindowState w = mWmService.mPendingRemoveTmp[i];
+ w.removeImmediately();
+ final DisplayContent displayContent = w.getDisplayContent();
+ if (displayContent != null && !displayList.contains(displayContent)) {
+ displayList.add(displayContent);
+ }
+ }
+
+ for (int j = displayList.size() - 1; j >= 0; --j) {
+ final DisplayContent dc = displayList.get(j);
+ dc.assignWindowLayers(true /*setLayoutNeeded*/);
+ }
+ }
+
forAllDisplays(dc -> {
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9b94f44..f26c539 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
@@ -114,6 +115,7 @@
private String mRelayoutTag;
private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
+ final boolean mSetsUnrestrictedKeepClearAreas;
public Session(WindowManagerService service, IWindowSessionCallback callback) {
mService = service;
@@ -132,6 +134,9 @@
== PERMISSION_GRANTED;
mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
+ mSetsUnrestrictedKeepClearAreas =
+ service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
+ == PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
@@ -813,7 +818,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 +834,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);
}
@@ -880,8 +885,16 @@
}
@Override
- public void setOnBackInvokedCallback(IWindow iWindow,
- IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException {
- // TODO: Set the callback to the WindowState of the window.
+ public void setOnBackInvokedCallback(IWindow window,
+ IOnBackInvokedCallback onBackInvokedCallback) throws RemoteException {
+ synchronized (mService.mGlobalLock) {
+ WindowState windowState = mService.windowForClientLocked(this, window, false);
+ if (windowState == null) {
+ Slog.e(TAG_WM,
+ "setOnBackInvokedCallback(): Can't find window state for window:" + window);
+ } else {
+ windowState.setOnBackInvokedCallback(onBackInvokedCallback);
+ }
+ }
}
}
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..b84ef77 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2197,10 +2197,6 @@
final Rect taskBounds = getBounds();
width = taskBounds.width();
height = taskBounds.height();
-
- final int outset = getTaskOutset();
- width += 2 * outset;
- height += 2 * outset;
}
if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
return;
@@ -2209,28 +2205,6 @@
mLastSurfaceSize.set(width, height);
}
- /**
- * Calculate an amount by which to expand the task bounds in each direction.
- * Used to make room for shadows in the pinned windowing mode.
- */
- int getTaskOutset() {
- // If we are drawing shadows on the task then don't outset the root task.
- if (mWmService.mRenderShadowsInCompositor) {
- return 0;
- }
- DisplayContent displayContent = getDisplayContent();
- if (inPinnedWindowingMode() && displayContent != null) {
- final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
-
- // We multiply by two to match the client logic for converting view elevation
- // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
- return (int) Math.ceil(
- mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
- * 2);
- }
- return 0;
- }
-
@VisibleForTesting
Point getLastSurfaceSize() {
return mLastSurfaceSize;
@@ -3434,6 +3408,9 @@
// Whether the direct top activity is in size compat mode on foreground.
info.topActivityInSizeCompat = isTopActivityResumed
&& mReuseActivitiesReport.top.inSizeCompatMode();
+ // Whether the direct top activity is eligible for letterbox education.
+ info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
+ && mReuseActivitiesReport.top.isEligibleForLetterboxEducation();
// Whether the direct top activity requested showing camera compat control.
info.cameraCompatControlState = isTopActivityResumed
? mReuseActivitiesReport.top.getCameraCompatControlState()
@@ -4335,7 +4312,7 @@
*/
private void updateShadowsRadius(boolean taskIsFocused,
SurfaceControl.Transaction pendingTransaction) {
- if (!mWmService.mRenderShadowsInCompositor || !isRootTask()) return;
+ if (!isRootTask()) return;
final float newShadowRadius = getShadowRadius(taskIsFocused);
if (mShadowRadius != newShadowRadius) {
@@ -6012,14 +5989,6 @@
scheduleAnimation();
}
- @Override
- void getRelativePosition(Point outPos) {
- super.getRelativePosition(outPos);
- final int outset = getTaskOutset();
- outPos.x -= outset;
- outPos.y -= outset;
- }
-
private Point getRelativePosition() {
Point position = new Point();
getRelativePosition(position);
@@ -6588,4 +6557,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/TaskSystemBarsListenerController.java b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
new file mode 100644
index 0000000..acb6061
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * Manages dispatch of task system bar changes to interested listeners. All invocations must be
+ * performed while the {@link WindowManagerService#getWindowManagerLock() Window Manager Lock} is
+ * held.
+ */
+final class TaskSystemBarsListenerController {
+
+ private final HashSet<TaskSystemBarsListener> mListeners = new HashSet<>();
+ private final Executor mBackgroundExecutor;
+
+ TaskSystemBarsListenerController() {
+ this.mBackgroundExecutor = BackgroundThread.getExecutor();
+ }
+
+ void registerListener(TaskSystemBarsListener listener) {
+ mListeners.add(listener);
+ }
+
+ void unregisterListener(TaskSystemBarsListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void dispatchTransientSystemBarVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar) {
+ HashSet<TaskSystemBarsListener> localListeners;
+ localListeners = new HashSet<>(mListeners);
+
+ mBackgroundExecutor.execute(() -> {
+ for (TaskSystemBarsListener listener : localListeners) {
+ listener.onTransientSystemBarsVisibilityChanged(
+ taskId,
+ visible,
+ wereRevealedFromSwipeOnSystemBar);
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index ded58f4..ad45948 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1039,6 +1039,8 @@
" Rejecting as detached: %s", wc);
continue;
}
+ // The level of transition target should be at least window token.
+ if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
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..4900f92 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
@@ -255,6 +256,25 @@
}
/**
+ * An interface to be notified when the system bars for a task change.
+ */
+ public interface TaskSystemBarsListener {
+
+ /**
+ * Called when the visibility of the system bars of a task change.
+ *
+ * @param taskId the identifier of the task.
+ * @param visible if the transient system bars are visible.
+ * @param wereRevealedFromSwipeOnSystemBar if the transient bars were revealed due to a
+ * swipe gesture on a system bar.
+ */
+ void onTransientSystemBarsVisibilityChanged(
+ int taskId,
+ boolean visible,
+ boolean wereRevealedFromSwipeOnSystemBar);
+ }
+
+ /**
* An interface to be notified when keyguard exit animation should start.
*/
public interface KeyguardExitAnimationStartListener {
@@ -518,6 +538,20 @@
public abstract void registerAppTransitionListener(AppTransitionListener listener);
/**
+ * Registers a listener to be notified to when the system bars of a task changes.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+ /**
+ * Registers a listener to be notified to when the system bars of a task changes.
+ *
+ * @param listener The listener to unregister.
+ */
+ public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener);
+
+ /**
* Registers a listener to be notified to start the keyguard exit animation.
*
* @param listener The listener to register.
@@ -796,8 +830,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..22c430f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -222,6 +222,7 @@
import android.util.EventLog;
import android.util.MergedConfiguration;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.TypedValue;
@@ -461,11 +462,6 @@
int mVr2dDisplayId = INVALID_DISPLAY;
boolean mVrModeEnabled = false;
- /* If true, shadows drawn around the window will be rendered by the system compositor. If
- * false, shadows will be drawn by the client by setting an elevation on the root view and
- * the contents will be inset by the shadow radius. */
- boolean mRenderShadowsInCompositor = false;
-
/**
* Tracks a map of input tokens to info that is used to decide whether to intercept
* a key event.
@@ -586,6 +582,20 @@
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
/**
+ * Windows whose animations have ended and now must be removed.
+ */
+ final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
+
+ /**
+ * Used when processing mPendingRemove to avoid working on the original array.
+ */
+ WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+ // TODO: use WindowProcessController once go/wm-unified is done.
+ /** Mapping of process pids to configurations */
+ final SparseArray<Configuration> mProcessConfigurations = new SparseArray<>();
+
+ /**
* Mapping of displayId to {@link DisplayImePolicy}.
* Note that this can be accessed without holding the lock.
*/
@@ -683,6 +693,7 @@
() -> mDisplayRotationController = null;
final DisplayWindowListenerController mDisplayNotificationController;
+ final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
boolean mDisplayFrozen = false;
long mDisplayFreezeTime = 0;
@@ -795,8 +806,6 @@
resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(mDevEnableNonResizableMultiWindowUri, false, this,
UserHandle.USER_ALL);
- resolver.registerContentObserver(mRenderShadowsInCompositorUri, false, this,
- UserHandle.USER_ALL);
resolver.registerContentObserver(mDisplaySettingsPathUri, false, this,
UserHandle.USER_ALL);
}
@@ -837,11 +846,6 @@
return;
}
- if (mRenderShadowsInCompositorUri.equals(uri)) {
- setShadowRenderer();
- return;
- }
-
if (mDisplaySettingsPathUri.equals(uri)) {
updateDisplaySettingsLocation();
return;
@@ -956,11 +960,6 @@
}
}
- private void setShadowRenderer() {
- mRenderShadowsInCompositor = Settings.Global.getInt(mContext.getContentResolver(),
- DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
- }
-
PowerManager mPowerManager;
PowerManagerInternal mPowerManagerInternal;
@@ -1265,6 +1264,7 @@
mScreenFrozenLock.setReferenceCounted(false);
mDisplayNotificationController = new DisplayWindowListenerController(this);
+ mTaskSystemBarsListenerController = new TaskSystemBarsListenerController();
mActivityManager = ActivityManager.getService();
mActivityTaskManager = ActivityTaskManager.getService();
@@ -1378,7 +1378,6 @@
float[] spotColor = {0.f, 0.f, 0.f, spotShadowAlpha};
SurfaceControl.setGlobalShadowSettings(ambientColor, spotColor, lightY, lightZ,
lightRadius);
- setShadowRenderer();
}
/**
@@ -2036,6 +2035,7 @@
dc.mWinRemovedSinceNullFocus.add(win);
}
mEmbeddedWindowController.onWindowRemoved(win);
+ mPendingRemove.remove(win);
mResizingWindows.remove(win);
updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
mWindowsChanged = true;
@@ -3837,14 +3837,20 @@
}
/**
- * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. The returned
- * bitmap will be full size and will not include any secure content.
+ * Generates and returns an up-to-date {@link Bitmap} for the specified taskId.
*
- * @param taskId The task ID of the task for which a snapshot is requested.
+ * @param taskId The task ID of the task for which a Bitmap is requested.
+ * @param layerCaptureArgsBuilder A {@link SurfaceControl.LayerCaptureArgs.Builder} with
+ * arguments for how to capture the Bitmap. The caller can
+ * specify any arguments, but this method will ensure that the
+ * specified task's SurfaceControl is used and the crop is set to
+ * the bounds of that task.
* @return The Bitmap, or null if no task with the specified ID can be found or the bitmap could
* not be generated.
*/
- @Nullable public Bitmap captureTaskBitmap(int taskId) {
+ @Nullable
+ public Bitmap captureTaskBitmap(int taskId,
+ @NonNull SurfaceControl.LayerCaptureArgs.Builder layerCaptureArgsBuilder) {
if (mTaskSnapshotController.shouldDisableSnapshots()) {
return null;
}
@@ -3858,9 +3864,7 @@
task.getBounds(mTmpRect);
final SurfaceControl sc = task.getSurfaceControl();
final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers(
- new SurfaceControl.LayerCaptureArgs.Builder(sc)
- .setSourceCrop(mTmpRect)
- .build());
+ layerCaptureArgsBuilder.setLayer(sc).setSourceCrop(mTmpRect).build());
if (buffer == null) {
Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId);
return null;
@@ -6364,6 +6368,23 @@
}
}
}
+ if (mPendingRemove.size() > 0) {
+ pw.println();
+ pw.println(" Remove pending for:");
+ for (int i=mPendingRemove.size()-1; i>=0; i--) {
+ WindowState w = mPendingRemove.get(i);
+ if (windows == null || windows.contains(w)) {
+ pw.print(" Remove #"); pw.print(i); pw.print(' ');
+ pw.print(w);
+ if (dumpAll) {
+ pw.println(":");
+ w.dump(pw, " ", true);
+ } else {
+ pw.println();
+ }
+ }
+ }
+ }
if (mForceRemoves != null && mForceRemoves.size() > 0) {
pw.println();
pw.println(" Windows force removing:");
@@ -6622,6 +6643,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 +6730,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 +6799,10 @@
if (dumpAll) {
pw.println(separator);
}
+ mAtmService.dumpInstalledPackagesConfig(pw);
+ if (dumpAll) {
+ pw.println(separator);
+ }
mConstants.dump(pw);
}
}
@@ -7583,6 +7612,20 @@
}
@Override
+ public void registerTaskSystemBarsListener(TaskSystemBarsListener listener) {
+ synchronized (mGlobalLock) {
+ mTaskSystemBarsListenerController.registerListener(listener);
+ }
+ }
+
+ @Override
+ public void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener) {
+ synchronized (mGlobalLock) {
+ mTaskSystemBarsListenerController.unregisterListener(listener);
+ }
+ }
+
+ @Override
public void registerKeyguardExitAnimationStartListener(
KeyguardExitAnimationStartListener listener) {
synchronized (mGlobalLock) {
@@ -7949,6 +7992,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 +8338,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 +8347,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 +8626,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 +8638,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 +8672,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 +8686,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 +8697,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 +8706,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/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0f8587c..1cf4c1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -822,6 +822,29 @@
return 0;
}
+ private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsEducationEnabled(enabled);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -859,6 +882,9 @@
case "--defaultPositionForReachability":
runSetLetterboxDefaultPositionForReachability(pw);
break;
+ case "--isEducationEnabled":
+ runSetLetterboxIsEducationEnabled(pw);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -903,6 +929,9 @@
case "defaultPositionForReachability":
mLetterboxConfiguration.getDefaultPositionForReachability();
break;
+ case "isEducationEnabled":
+ mLetterboxConfiguration.getIsEducationEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -998,6 +1027,7 @@
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsReachabilityEnabled();
mLetterboxConfiguration.resetDefaultPositionForReachability();
+ mLetterboxConfiguration.resetIsEducationEnabled();
}
}
@@ -1014,6 +1044,8 @@
pw.println("Default position for reachability: "
+ LetterboxConfiguration.letterboxReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForReachability()));
+ pw.println("Is education enabled: "
+ + mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1154,10 +1186,12 @@
pw.println(" --defaultPositionForReachability [left|center|right]");
pw.println(" Default horizontal position of app window when reachability is.");
pw.println(" enabled.");
+ pw.println(" --isEducationEnabled [true|1|false|0]");
+ pw.println(" Whether education is allowed for letterboxed fullscreen apps.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled");
- pw.println(" |defaultPositionMultiplierForReachability]");
+ pw.println(" isEducationEnabled||defaultPositionMultiplierForReachability]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8864b98..0d72e9a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -106,6 +106,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -245,6 +246,7 @@
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.window.ClientWindowFrames;
+import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.KeyInterceptionInfo;
@@ -845,6 +847,11 @@
}
};
+ /**
+ * @see #setOnBackInvokedCallback(IOnBackInvokedCallback)
+ */
+ private IOnBackInvokedCallback mOnBackInvokedCallback;
+
@Override
WindowState asWindowState() {
return this;
@@ -1061,6 +1068,22 @@
return true;
}
+ /**
+ * Used by {@link android.window.WindowOnBackInvokedDispatcher} to set the callback to be
+ * called when a back navigation action is initiated.
+ * @see BackNavigationController
+ */
+ void setOnBackInvokedCallback(@Nullable IOnBackInvokedCallback onBackInvokedCallback) {
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "%s: Setting back callback %s",
+ this, onBackInvokedCallback);
+ mOnBackInvokedCallback = onBackInvokedCallback;
+ }
+
+ @Nullable
+ IOnBackInvokedCallback getOnBackInvokedCallback() {
+ return mOnBackInvokedCallback;
+ }
+
interface PowerManagerWrapper {
void wakeUp(long time, @WakeReason int reason, String details);
@@ -3848,6 +3871,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
@@ -4883,20 +4910,15 @@
if (hasSurface) {
mWmService.mDestroySurface.add(this);
}
+ if (mRemoveOnExit) {
+ mWmService.mPendingRemove.add(this);
+ mRemoveOnExit = false;
+ }
}
mAnimatingExit = false;
getDisplayContent().mWallpaperController.hideWallpapers(this);
}
- @Override
- boolean handleCompleteDeferredRemoval() {
- if (mRemoveOnExit) {
- mRemoveOnExit = false;
- removeImmediately();
- }
- return super.handleCompleteDeferredRemoval();
- }
-
boolean clearAnimatingFlags() {
boolean didSomething = false;
// We don't want to clear it out for windows that get replaced, because the
@@ -5399,17 +5421,6 @@
outPoint.offset(-parentBounds.left, -parentBounds.top);
}
- Task rootTask = getRootTask();
-
- // If we have root task outsets, that means the top-left
- // will be outset, and we need to inset ourselves
- // to account for it. If we actually have shadows we will
- // then un-inset ourselves by the surfaceInsets.
- if (rootTask != null) {
- final int outset = rootTask.getTaskOutset();
- outPoint.offset(outset, outset);
- }
-
// The surface size is larger than the window if the window has positive surface insets.
transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
outPoint.offset(-mTmpPoint.x, -mTmpPoint.y);
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/Android.bp b/services/core/jni/Android.bp
index 95ef5f7..99abf44 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -103,6 +103,7 @@
"libappfuse",
"libbinder_ndk",
"libbinder",
+ "libchrome",
"libcutils",
"libcrypto",
"liblog",
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/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 43018a9..adc91fc 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -186,7 +186,7 @@
};
/** Creates a new uinput device and assigns a file descriptor. */
-static int openUinput(const char* readableName, jint vendorId, jint productId,
+static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys,
DeviceType deviceType, jint screenHeight, jint screenWidth) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
if (fd < 0) {
@@ -194,6 +194,8 @@
return -errno;
}
+ ioctl(fd, UI_SET_PHYS, phys);
+
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_SYN);
switch (deviceType) {
@@ -295,28 +297,30 @@
return fd.release();
}
-static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys,
DeviceType deviceType, int screenHeight, int screenWidth) {
ScopedUtfChars readableName(env, name);
- return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
- screenWidth);
+ ScopedUtfChars readablePhys(env, phys);
+ return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType,
+ screenHeight, screenWidth);
}
static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
- /* screenWidth */ 0);
+ jint productId, jstring phys) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE,
+ /* screenHeight */ 0, /* screenWidth */ 0);
}
static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
- jint productId, jint height, jint width) {
- return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+ jint productId, jstring phys, jint height, jint width) {
+ return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height,
+ width);
}
static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
@@ -435,9 +439,11 @@
}
static JNINativeMethod methods[] = {
- {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
- {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
- {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+ {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputKeyboard},
+ {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I",
+ (void*)nativeOpenUinputMouse},
+ {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I",
(void*)nativeOpenUinputTouchscreen},
{"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
{"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index df5fb28..e5529f1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -265,7 +265,6 @@
base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env,
const std::string& name);
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId,
- bool isGestureMonitor,
const std::string& name,
int32_t pid);
status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
@@ -457,6 +456,9 @@
mInputManager->getReader().dump(dump);
dump += "\n";
+ mInputManager->getUnwantedInteractionBlocker().dump(dump);
+ dump += "\n";
+
mInputManager->getClassifier().dump(dump);
dump += "\n";
@@ -519,11 +521,9 @@
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
- JNIEnv* /* env */, int32_t displayId, bool isGestureMonitor, const std::string& name,
- int32_t pid) {
+ JNIEnv* /* env */, int32_t displayId, const std::string& name, int32_t pid) {
ATRACE_CALL();
- return mInputManager->getDispatcher().createInputMonitor(displayId, isGestureMonitor, name,
- pid);
+ return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
}
status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */,
@@ -704,6 +704,7 @@
void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
ATRACE_CALL();
+ mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices);
JNIEnv* env = jniEnv();
size_t count = inputDevices.size();
@@ -1655,7 +1656,7 @@
}
static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId,
- jboolean isGestureMonitor, jstring nameObj, jint pid) {
+ jstring nameObj, jint pid) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
if (displayId == ADISPLAY_ID_NONE) {
@@ -1668,7 +1669,7 @@
std::string name = nameChars.c_str();
base::Result<std::unique_ptr<InputChannel>> inputChannel =
- im->createInputMonitor(env, displayId, isGestureMonitor, name, pid);
+ im->createInputMonitor(env, displayId, name, pid);
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -2377,7 +2378,7 @@
{"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
{"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
(void*)nativeCreateInputChannel},
- {"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;",
+ {"nativeCreateInputMonitor", "(JILjava/lang/String;I)Landroid/view/InputChannel;",
(void*)nativeCreateInputMonitor},
{"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
{"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers},
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0da8f7e..0584604 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -244,6 +244,9 @@
return hasLatLong(location.v1_0);
}
+bool isSvStatusRegistered = false;
+bool isNmeaRegistered = false;
+
} // namespace
static inline jboolean boolToJbool(bool value) {
@@ -505,6 +508,13 @@
template <class T_list, class T_sv_info>
Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) {
+ // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
+ if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() <= 1) {
+ if (!isSvStatusRegistered) {
+ return Void();
+ }
+ }
+
JNIEnv* env = getJniEnv();
uint32_t listSize = getGnssSvInfoListSize(svStatus);
@@ -566,8 +576,12 @@
return Void();
}
-Return<void> GnssCallback::gnssNmeaCb(
- int64_t timestamp, const ::android::hardware::hidl_string& nmea) {
+Return<void> GnssCallback::gnssNmeaCb(int64_t timestamp,
+ const ::android::hardware::hidl_string& nmea) {
+ // In HIDL, if no listener is registered, do not report nmea to the framework.
+ if (!isNmeaRegistered) {
+ return Void();
+ }
JNIEnv* env = getJniEnv();
/*
* The Java code will call back to read these values.
@@ -680,6 +694,12 @@
}
Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
+ // In AIDL v1, if no listener is registered, do not report nmea to the framework.
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() <= 1) {
+ if (!isNmeaRegistered) {
+ return Status::ok();
+ }
+ }
JNIEnv* env = getJniEnv();
/*
* The Java code will call back to read these values.
@@ -1504,11 +1524,14 @@
JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
- auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode),
- static_cast<IGnssAidl::GnssPositionRecurrence>(
- recurrence),
- min_interval, preferred_accuracy, preferred_time,
- low_power_mode);
+ IGnssAidl::PositionModeOptions options;
+ options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+ options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+ options.minIntervalMs = min_interval;
+ options.preferredAccuracyMeters = preferred_accuracy;
+ options.preferredTimeMs = preferred_time;
+ options.lowPowerMode = low_power_mode;
+ auto status = gnssHalAidl->setPositionMode(options);
return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
}
@@ -1559,6 +1582,58 @@
return checkHidlReturn(result, "IGnss stop() failed.");
}
+static jboolean android_location_gnss_hal_GnssNative_start_sv_status_collection(JNIEnv* /* env */,
+ jclass) {
+ isSvStatusRegistered = true;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->startSvStatus();
+ return checkAidlStatus(status, "IGnssAidl startSvStatus() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_sv_status_collection(JNIEnv* /* env */,
+ jclass) {
+ isSvStatusRegistered = false;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->stopSvStatus();
+ return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_start_nmea_message_collection(
+ JNIEnv* /* env */, jclass) {
+ isNmeaRegistered = true;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->startNmea();
+ return checkAidlStatus(status, "IGnssAidl startNmea() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean android_location_gnss_hal_GnssNative_stop_nmea_message_collection(JNIEnv* /* env */,
+ jclass) {
+ isNmeaRegistered = false;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ auto status = gnssHalAidl->stopNmea();
+ return checkAidlStatus(status, "IGnssAidl stopNmea() failed.");
+ }
+ if (gnssHal == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass,
jint flags) {
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1986,7 +2061,7 @@
jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
uint16_t corrFlags = static_cast<uint16_t>(correctionFlags);
- jobject reflectingPlaneObj;
+ jobject reflectingPlaneObj = nullptr;
bool has_ref_plane = (corrFlags & GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE) != 0;
if (has_ref_plane) {
reflectingPlaneObj =
@@ -2010,6 +2085,7 @@
.azimuthDegrees = azimuthDegreeRefPlane,
};
}
+ env->DeleteLocalRef(reflectingPlaneObj);
SingleSatCorrection_V1_0 singleSatCorrection = {
.singleSatCorrectionFlags = corrFlags,
@@ -2041,6 +2117,7 @@
};
list[i] = singleSatCorrection_1_1;
+ env->DeleteLocalRef(singleSatCorrectionObj);
}
}
@@ -2058,6 +2135,7 @@
singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType),
list[i] = singleSatCorrection;
+ env->DeleteLocalRef(singleSatCorrectionObj);
}
}
@@ -2128,6 +2206,7 @@
hidl_vec<SingleSatCorrection_V1_0> list(len);
getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list);
+ env->DeleteLocalRef(singleSatCorrectionList);
measurementCorrections_1_0.satCorrections = list;
auto result = gnssCorrectionsIface_V1_0->setCorrections(measurementCorrections_1_0);
@@ -2362,6 +2441,16 @@
{"native_is_gnss_visibility_control_supported", "()Z",
reinterpret_cast<void*>(
android_location_gnss_hal_GnssNative_is_gnss_visibility_control_supported)},
+ {"native_start_sv_status_collection", "()Z",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_sv_status_collection)},
+ {"native_stop_sv_status_collection", "()Z",
+ reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_stop_sv_status_collection)},
+ {"native_start_nmea_message_collection", "()Z",
+ reinterpret_cast<void*>(
+ android_location_gnss_hal_GnssNative_start_nmea_message_collection)},
+ {"native_stop_nmea_message_collection", "()Z",
+ reinterpret_cast<void*>(
+ android_location_gnss_hal_GnssNative_stop_nmea_message_collection)},
};
static const JNINativeMethod sBatchingMethods[] = {
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 5178132..f8a8168 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -16,20 +16,18 @@
#define LOG_TAG "NetworkStatsNative"
+#include <cutils/qtaguid.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include "core_jni_helpers.h"
#include <jni.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <utils/misc.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <utils/Log.h>
+#include <utils/misc.h>
-#include "android-base/unique_fd.h"
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
@@ -104,10 +102,15 @@
}
}
+static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) {
+ return qtaguid_deleteTagData(0, uid);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
{"nativeGetUidStat", "(II)J", (void*)getUidStat},
+ {"nativeDeleteTagData", "(I)I", (void*)deleteTagData},
};
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index d760b4d..424ffd4 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -41,7 +41,7 @@
jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) {
JNIEnv* env = getJniEnv();
ScopedJniString jniSetId{env, setid_string};
- auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str());
+ auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str());
return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed.");
}
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
index fbc000b..99d06eb 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
@@ -226,17 +226,18 @@
env->NewObject(class_gnssAntennaInfoBuilder, method_gnssAntennaInfoBuilderCtor);
// Set fields
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
- gnssAntennaInfo.carrierFrequencyMHz);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetPhaseCenterOffset, phaseCenterOffset);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
- phaseCenterVariationCorrections);
- env->CallObjectMethod(gnssAntennaInfoBuilderObject,
- method_gnssAntennaInfoBuilderSetSignalGainCorrections,
- signalGainCorrections);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
+ gnssAntennaInfo.carrierFrequencyMHz);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetPhaseCenterOffset,
+ phaseCenterOffset);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections,
+ phaseCenterVariationCorrections);
+ callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
+ method_gnssAntennaInfoBuilderSetSignalGainCorrections,
+ signalGainCorrections);
// build
jobject gnssAntennaInfoObject =
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index fbdeec6..34ca559 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -212,13 +212,14 @@
jobject gnssMeasurementsEventBuilderObject =
env->NewObject(class_gnssMeasurementsEventBuilder,
method_gnssMeasurementsEventBuilderCtor);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetClock, clock);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray);
- env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
- method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
- gnssAgcArray);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetClock, clock);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetMeasurements,
+ measurementArray);
+ callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+ method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
+ gnssAgcArray);
jobject gnssMeasurementsEventObject =
env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
method_gnssMeasurementsEventBuilderBuild);
@@ -359,9 +360,7 @@
jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements);
jobjectArray gnssAgcArray = nullptr;
- if (data.gnssAgcs.has_value()) {
- gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value());
- }
+ gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
env->DeleteLocalRef(clock);
@@ -410,24 +409,24 @@
satellitePvt.satClockInfo.satHardwareCodeBiasMeters,
satellitePvt.satClockInfo.satTimeCorrectionMeters,
satellitePvt.satClockInfo.satClkDriftMps);
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetPositionEcef, positionEcef);
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
- env->CallObjectMethod(satellitePvtBuilderObject, method_satellitePvtBuilderSetClockInfo,
- clockInfo);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetPositionEcef, positionEcef);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetVelocityEcef, velocityEcef);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetClockInfo, clockInfo);
}
if (satFlags & SatellitePvt::HAS_IONO) {
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetIonoDelayMeters,
- satellitePvt.ionoDelayMeters);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetIonoDelayMeters,
+ satellitePvt.ionoDelayMeters);
}
if (satFlags & SatellitePvt::HAS_TROPO) {
- env->CallObjectMethod(satellitePvtBuilderObject,
- method_satellitePvtBuilderSetTropoDelayMeters,
- satellitePvt.tropoDelayMeters);
+ callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
+ method_satellitePvtBuilderSetTropoDelayMeters,
+ satellitePvt.tropoDelayMeters);
}
jobject satellitePvtObject =
@@ -455,17 +454,19 @@
jobject correlationVectorBuilderObject =
env->NewObject(class_correlationVectorBuilder,
method_correlationVectorBuilderCtor);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetMagnitude, magnitudeArray);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
- correlationVector.frequencyOffsetMps);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetSamplingStartMeters,
- correlationVector.samplingStartM);
- env->CallObjectMethod(correlationVectorBuilderObject,
- method_correlationVectorBuilderSetSamplingWidthMeters,
- correlationVector.samplingWidthM);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetMagnitude,
+ magnitudeArray);
+ callObjectMethodIgnoringResult(
+ env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond,
+ correlationVector.frequencyOffsetMps);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetSamplingStartMeters,
+ correlationVector.samplingStartM);
+ callObjectMethodIgnoringResult(env, correlationVectorBuilderObject,
+ method_correlationVectorBuilderSetSamplingWidthMeters,
+ correlationVector.samplingWidthM);
jobject correlationVectorObject =
env->CallObjectMethod(correlationVectorBuilderObject,
method_correlationVectorBuilderBuild);
@@ -508,8 +509,8 @@
return gnssMeasurementArray;
}
-jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) {
+jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<GnssAgc>& agcs) {
if (agcs.size() == 0) {
return nullptr;
}
@@ -518,18 +519,17 @@
env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */);
for (uint16_t i = 0; i < agcs.size(); ++i) {
- if (!agcs[i].has_value()) {
- continue;
- }
- const GnssAgc& gnssAgc = agcs[i].value();
+ const GnssAgc& gnssAgc = agcs[i];
jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
- gnssAgc.agcLevelDb);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType,
- (int)gnssAgc.constellation);
- env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz,
- gnssAgc.carrierFrequencyHz);
+ callObjectMethodIgnoringResult(env, agcBuilderObject, method_gnssAgcBuilderSetLevelDb,
+ gnssAgc.agcLevelDb);
+ callObjectMethodIgnoringResult(env, agcBuilderObject,
+ method_gnssAgcBuilderSetConstellationType,
+ (int)gnssAgc.constellation);
+ callObjectMethodIgnoringResult(env, agcBuilderObject,
+ method_gnssAgcBuilderSetCarrierFrequencyHz,
+ gnssAgc.carrierFrequencyHz);
jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild);
env->SetObjectArrayElement(gnssAgcArray, i, agcObject);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index 9b34631..17af949 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -62,8 +62,8 @@
jobjectArray translateAllGnssMeasurements(
JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements);
- jobjectArray translateAllGnssAgcs(
- JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs);
+ jobjectArray translateAllGnssAgcs(JNIEnv* env,
+ const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs);
void translateAndSetGnssData(const hardware::gnss::GnssData& data);
diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp
index 40a94ce..8f32c47 100644
--- a/services/core/jni/gnss/Utils.cpp
+++ b/services/core/jni/gnss/Utils.cpp
@@ -111,6 +111,13 @@
}
}
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+ va_list args;
+ va_start(args, mid);
+ env->DeleteLocalRef(env->CallObjectMethodV(obj, mid, args));
+ va_end(args);
+}
+
JavaObject::JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor)
: env_(env), clazz_(clazz) {
object_ = env_->NewObject(clazz_, defaultCtor);
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 2640a77..c8ee661 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -56,6 +56,8 @@
void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...);
+
template <class T>
void logHidlError(hardware::Return<T>& result, const char* errorMessage) {
ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
diff --git a/services/core/lint-baseline.xml b/services/core/lint-baseline.xml
index c5b0549..69e13b3 100644
--- a/services/core/lint-baseline.xml
+++ b/services/core/lint-baseline.xml
@@ -1,158 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev">
+<issues format="5" by="lint 7.2.0-dev">
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/biometrics/BiometricService.java"
- line="1106"
- column="20"/>
+ line="1122"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" return Settings.Secure.getInt(context.getContentResolver(),"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/biometrics/BiometricService.java"
- line="1111"
- column="20"/>
+ line="1127"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
- errorLine1=" return Settings.Secure.getString(mContentResolver, mKey);"
- errorLine2=" ~~~~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
+ <location
+ file="packages/modules/Bluetooth/service/java/com/android/server/bluetooth/BluetoothManagerService.java"
+ line="670"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
+ <location
+ file="packages/modules/Bluetooth/service/java/com/android/server/bluetooth/BluetoothManagerService.java"
+ line="678"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
+ <location
+ file="packages/modules/Bluetooth/service/java/com/android/server/bluetooth/BluetoothManagerService.java"
+ line="679"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
+ <location
+ file="packages/modules/Bluetooth/service/java/com/android/server/bluetooth/BluetoothManagerService.java"
+ line="696"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
+ <location
+ file="packages/modules/Bluetooth/service/java/com/android/server/bluetooth/BluetoothManagerService.java"
+ line="705"/>
+ </issue>
+
+ <issue
+ id="NonUserGetterCalled"
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/CertBlacklister.java"
- line="73"
- column="36"/>
+ line="73"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" }"
- errorLine2=" ^">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/clipboard/ClipboardService.java"
- line="973"
- column="7"/>
+ line="1072"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" boolean accessibilityEnabled = Settings.Secure.getInt(cr,"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/DockObserver.java"
- line="176"
- column="60"/>
+ line="260"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
- errorLine1=" String mediaButtonReceiverInfo = Settings.Secure.getString(mContentResolver,"
- errorLine2=" ~~~~~~~~~">
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/media/MediaSessionService.java"
- line="928"
- column="46"/>
+ line="928"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" final boolean isSecureFrpEnabled ="
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java"
- line="1959"
- column="36"/>
+ line="1902"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" private int getUnknownSourcesSettings() {"
- errorLine2=" ~~~~~~">
- <location
- file="frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java"
- line="16741"
- column="39"/>
- </issue>
-
- <issue
- id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
- errorLine1=" String inputMethodComponent = Settings.Secure.getString(mContext.getContentResolver(),"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/services/core/java/com/android/server/SensorPrivacyService.java"
- line="489"
- column="35"/>
- </issue>
-
- <issue
- id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. "
- errorLine1=" return Settings.Secure.getString(getContentResolverAsUser(userId), key);"
- errorLine2=" ~~~~~~~~~">
+ message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java"
- line="1994"
- column="12"/>
+ line="2069"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" return Settings.Secure.getInt(getContentResolverAsUser(userId), key, def);"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java"
- line="2001"
- column="12"/>
+ line="2076"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" if (Settings.Secure.getInt(mContext.getContentResolver(),"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java"
- line="980"
- column="45"/>
+ line="984"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" && Settings.Secure.getInt(mContext.getContentResolver(),"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java"
- line="1442"
- column="36"/>
+ line="1446"/>
</issue>
<issue
id="NonUserGetterCalled"
- message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. "
- errorLine1=" && Settings.Secure.getInt(mContext.getContentResolver(),"
- errorLine2=" ~~~~~~">
+ message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. ">
<location
file="frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java"
- line="1444"
- column="36"/>
+ line="1448"/>
</issue>
</issues>
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 574dbfd..be0ddc1 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -38,6 +38,10 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="thermalThrottling" name="thermalThrottling">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
@@ -119,6 +123,18 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <!-- The minimum HDR video size at which high-brightness-mode is allowed to operate.
+ Default is 0.5 if not specified-->
+ <xs:element name="minimumHdrPercentOfScreen" type="nonNegativeDecimal"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- This LUT specifies how to boost HDR brightness at given SDR brightness (nits). -->
+ <xs:element type="sdrHdrRatioMap" name="sdrHdrRatioMap" minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:all>
<xs:attribute name="enabled" type="xs:boolean" use="optional"/>
</xs:complexType>
@@ -154,6 +170,45 @@
</xs:restriction>
</xs:simpleType>
+ <!-- Maps to DisplayDeviceConfig.INTERPOLATION_* values. -->
+ <xs:simpleType name="interpolation">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="linear"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:complexType name="thermalThrottling">
+ <xs:complexType>
+ <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessThrottlingMap">
+ <xs:sequence>
+ <xs:element name="brightnessThrottlingPoint" type="brightnessThrottlingPoint" maxOccurs="unbounded" minOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessThrottlingPoint">
+ <xs:sequence>
+ <xs:element type="thermalStatus" name="thermalStatus">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="brightness">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="nitsMap">
<xs:sequence>
<xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2">
@@ -161,6 +216,7 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
+ <xs:attribute name="interpolation" type="interpolation" use="optional"/>
</xs:complexType>
<xs:complexType name="point">
@@ -176,6 +232,28 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="sdrHdrRatioMap">
+ <xs:sequence>
+ <xs:element name="point" type="sdrHdrRatioPoint" maxOccurs="unbounded" minOccurs="2">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="sdrHdrRatioPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="sdrNits">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="hdrRatio">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:simpleType name="nonNegativeDecimal">
<xs:restriction base="xs:decimal">
<xs:minInclusive value="0.0"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 04f0916..2890d68 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -7,6 +7,19 @@
method public final void setMinimum(@NonNull java.math.BigDecimal);
}
+ public class BrightnessThrottlingMap {
+ ctor public BrightnessThrottlingMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessThrottlingPoint> getBrightnessThrottlingPoint();
+ }
+
+ public class BrightnessThrottlingPoint {
+ ctor public BrightnessThrottlingPoint();
+ method @NonNull public final java.math.BigDecimal getBrightness();
+ method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatus();
+ method public final void setBrightness(@NonNull java.math.BigDecimal);
+ method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
+ }
+
public class Density {
ctor public Density();
method @NonNull public final java.math.BigInteger getDensity();
@@ -39,6 +52,7 @@
method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+ method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
@@ -54,6 +68,7 @@
method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal);
+ method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
}
public class DisplayQuirks {
@@ -75,23 +90,35 @@
ctor public HighBrightnessMode();
method @NonNull public final boolean getAllowInLowPowerMode_all();
method public boolean getEnabled();
+ method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreen_all();
method @NonNull public final java.math.BigDecimal getMinimumLux_all();
method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_all();
+ method @Nullable public final com.android.server.display.config.SdrHdrRatioMap getSdrHdrRatioMap_all();
method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatusLimit_all();
method public com.android.server.display.config.HbmTiming getTiming_all();
method @NonNull public final java.math.BigDecimal getTransitionPoint_all();
method public final void setAllowInLowPowerMode_all(@NonNull boolean);
method public void setEnabled(boolean);
+ method public final void setMinimumHdrPercentOfScreen_all(@Nullable java.math.BigDecimal);
method public final void setMinimumLux_all(@NonNull java.math.BigDecimal);
method public final void setRefreshRate_all(@Nullable com.android.server.display.config.RefreshRateRange);
+ method public final void setSdrHdrRatioMap_all(@Nullable com.android.server.display.config.SdrHdrRatioMap);
method public final void setThermalStatusLimit_all(@NonNull com.android.server.display.config.ThermalStatus);
method public void setTiming_all(com.android.server.display.config.HbmTiming);
method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
}
+ public enum Interpolation {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.Interpolation _default;
+ enum_constant public static final com.android.server.display.config.Interpolation linear;
+ }
+
public class NitsMap {
ctor public NitsMap();
+ method public com.android.server.display.config.Interpolation getInterpolation();
method @NonNull public final java.util.List<com.android.server.display.config.Point> getPoint();
+ method public void setInterpolation(com.android.server.display.config.Interpolation);
}
public class Point {
@@ -110,6 +137,19 @@
method public final void setMinimum(java.math.BigInteger);
}
+ public class SdrHdrRatioMap {
+ ctor public SdrHdrRatioMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.SdrHdrRatioPoint> getPoint();
+ }
+
+ public class SdrHdrRatioPoint {
+ ctor public SdrHdrRatioPoint();
+ method @NonNull public final java.math.BigDecimal getHdrRatio();
+ method @NonNull public final java.math.BigDecimal getSdrNits();
+ method public final void setHdrRatio(@NonNull java.math.BigDecimal);
+ method public final void setSdrNits(@NonNull java.math.BigDecimal);
+ }
+
public class SensorDetails {
ctor public SensorDetails();
method @Nullable public final String getName();
@@ -131,6 +171,12 @@
enum_constant public static final com.android.server.display.config.ThermalStatus shutdown;
}
+ public class ThermalThrottling {
+ ctor public ThermalThrottling();
+ method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap();
+ method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
+ }
+
public class Thresholds {
ctor public Thresholds();
method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 26c442d..e18e002 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
@@ -83,7 +84,7 @@
private static final String ATTR_NEW_USER_DISCLAIMER = "new-user-disclaimer";
// Values of ATTR_NEW_USER_DISCLAIMER
- static final String NEW_USER_DISCLAIMER_SHOWN = "shown";
+ static final String NEW_USER_DISCLAIMER_ACKNOWLEDGED = "acked";
static final String NEW_USER_DISCLAIMER_NOT_NEEDED = "not_needed";
static final String NEW_USER_DISCLAIMER_NEEDED = "needed";
@@ -613,6 +614,28 @@
}
}
+ boolean isNewUserDisclaimerAcknowledged() {
+ if (mNewUserDisclaimer == null) {
+ if (mUserId == UserHandle.USER_SYSTEM) {
+ return true;
+ }
+ Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): mNewUserDisclaimer is null",
+ mUserId);
+ return false;
+ }
+ switch (mNewUserDisclaimer) {
+ case NEW_USER_DISCLAIMER_ACKNOWLEDGED:
+ case NEW_USER_DISCLAIMER_NOT_NEEDED:
+ return true;
+ case NEW_USER_DISCLAIMER_NEEDED:
+ return false;
+ default:
+ Slogf.w(TAG, "isNewUserDisclaimerAcknowledged(%d): invalid value %d", mUserId,
+ mNewUserDisclaimer);
+ return false;
+ }
+ }
+
void dump(IndentingPrintWriter pw) {
pw.println();
pw.println("Enabled Device Admins (User " + mUserId + ", provisioningState: "
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 733cfcd..8465770 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -56,6 +56,8 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
@@ -66,9 +68,12 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
@@ -129,6 +134,7 @@
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.provider.Telephony.Carriers.DPC_URI;
import static android.provider.Telephony.Carriers.ENFORCE_KEY;
@@ -217,6 +223,7 @@
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -3918,8 +3925,8 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller)
- || isPasswordLimitingAdminTargetingP(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller));
if (parent) {
Preconditions.checkCallAuthorization(
@@ -4772,7 +4779,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
return !isSeparateProfileChallengeEnabled(caller.getUserId());
@@ -4860,12 +4868,12 @@
enforceUserUnlocked(caller.getUserId());
if (parent) {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method on parent.");
} else {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || isDeviceOwner(caller) || isProfileOwner(caller),
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Must have " + REQUEST_PASSWORD_COMPLEXITY
+ " permission, or be a profile owner or device owner.");
}
@@ -4888,7 +4896,8 @@
"Provided complexity is not one of the allowed values.");
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
synchronized (getLockObject()) {
@@ -4968,7 +4977,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller));
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
@@ -5160,7 +5169,7 @@
}
// If caller has PO (or DO) throw or fail silently depending on its target SDK level.
- if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) {
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) {
@@ -5219,7 +5228,7 @@
return false;
}
- boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller);
+ boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller);
boolean doNotAskCredentialsOnBoot =
(flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
@@ -5406,7 +5415,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
// timeoutMs with value 0 means that the admin doesn't participate
// timeoutMs is clamped to the interval in case the internal constants change in the future
final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -5575,7 +5585,8 @@
}
private boolean canManageCaCerts(CallerIdentity caller) {
- return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+ return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
|| hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
}
@@ -5689,7 +5700,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
@@ -5754,7 +5765,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -5818,12 +5829,12 @@
}
private boolean canInstallCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
}
private boolean canChooseCertificates(CallerIdentity caller) {
- return isProfileOwner(caller) || isDeviceOwner(caller)
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_SELECTION);
}
@@ -5871,7 +5882,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION)));
final int granteeUid;
@@ -5984,7 +5995,7 @@
// If not, fall back to the device owner check.
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
}
@VisibleForTesting
@@ -6047,7 +6058,7 @@
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6182,7 +6193,7 @@
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
@@ -6337,14 +6348,15 @@
final int userId = caller.getUserId();
// Ensure calling process is device/profile owner.
if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
} else if (!Collections.disjoint(
scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -6434,7 +6446,8 @@
// * Either it's a profile owner / device owner, if componentName is provided
// * Or it's an app querying its own delegation scopes
if (caller.hasAdminComponent()) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
} else {
Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage),
String.format("Caller with uid %d is not %s", caller.getUid(),
@@ -6467,7 +6480,8 @@
// Retrieve the user ID of the calling process.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
return getDelegatePackagesInternalLocked(scope, caller.getUserId());
}
@@ -6600,7 +6614,8 @@
final CallerIdentity caller = getCallerIdentity(who);
// Ensure calling process is device/profile owner.
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(caller.getUserId());
@@ -6712,7 +6727,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
if (vpnPackage == null) {
@@ -6792,7 +6808,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId()));
@@ -6818,7 +6835,8 @@
caller = getCallerIdentity();
} else {
caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
return mInjector.binderWithCleanCallingIdentity(
@@ -6841,7 +6859,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId()));
@@ -6946,8 +6965,9 @@
+ "organization-owned device.");
}
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || calledByProfileOwnerOnOrgOwnedDevice,
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || calledByProfileOwnerOnOrgOwnedDevice
+ || isFinancedDeviceOwner(caller),
"Only device owners or profile owners of organization-owned device can set "
+ "WIPE_RESET_PROTECTION_DATA");
}
@@ -7139,8 +7159,8 @@
}
Preconditions.checkNotNull(who, "ComponentName is null");
CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager
.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
@@ -7189,7 +7209,8 @@
UserHandle.getUserId(frpManagementAgentUid));
} else {
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
admin = getProfileOwnerOrDeviceOwnerLocked(caller);
}
}
@@ -7617,7 +7638,7 @@
public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
@@ -7915,7 +7936,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7934,8 +7956,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -7955,7 +7976,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -7974,8 +7996,7 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isProfileOwner(caller)
- || isDeviceOwner(caller)
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY));
synchronized (getLockObject()) {
@@ -8067,7 +8088,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8091,7 +8112,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -8108,7 +8129,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -8132,7 +8153,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -8161,7 +8182,7 @@
// which could still contain data related to that user. Should we disallow that, e.g. until
// next boot? Might not be needed given that this still requires user consent.
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT);
@@ -8472,7 +8493,8 @@
}
Objects.requireNonNull(packageList, "packageList is null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES);
@@ -8501,7 +8523,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && isDefaultDeviceOwner(caller))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
@@ -8615,8 +8638,8 @@
@Override
public boolean hasDeviceOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || canManageUsers(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller)
|| hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
return mOwners.hasDeviceOwner();
}
@@ -8633,17 +8656,29 @@
}
}
- private boolean isDeviceOwner(CallerIdentity caller) {
+ /**
+ * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type
+ * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the
+ * case where the caller is not the device owner, there is no device owner, or the device owner
+ * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}.
+ *
+ */
+ private boolean isDefaultDeviceOwner(CallerIdentity caller) {
synchronized (getLockObject()) {
- if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
- return false;
- }
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+ }
+ }
- if (caller.hasAdminComponent()) {
- return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
- } else {
- return isUidDeviceOwnerLocked(caller.getUid());
- }
+ private boolean isDeviceOwnerLocked(CallerIdentity caller) {
+ if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) {
+ return false;
+ }
+
+ if (caller.hasAdminComponent()) {
+ return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName());
+ } else {
+ return isUidDeviceOwnerLocked(caller.getUid());
}
}
@@ -9073,8 +9108,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null));
@@ -9258,7 +9293,8 @@
final CallerIdentity caller = getCallerIdentity(who);
final int userId = caller.getUserId();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
Preconditions.checkCallingUser(isManagedProfile(userId));
synchronized (getLockObject()) {
@@ -9289,7 +9325,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setUserName(caller.getUserId(), profileName);
@@ -9725,7 +9762,8 @@
}
private void enforceCanCallLockTaskLocked(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userId = caller.getUserId();
if (!canUserUseLockTaskLocked(userId)) {
@@ -9982,7 +10020,8 @@
ComponentName activity) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10009,7 +10048,8 @@
public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
@@ -10030,7 +10070,7 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
if (parent) {
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -10070,7 +10110,7 @@
String packageName, Bundle settings) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
@@ -10168,7 +10208,8 @@
public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER);
synchronized (getLockObject()) {
@@ -10193,7 +10234,8 @@
public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
@@ -10242,7 +10284,8 @@
public void clearCrossProfileIntentFilters(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -10382,7 +10425,8 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -10495,7 +10539,8 @@
+ "system input methods when called on the parent instance of an "
+ "organization-owned device");
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
if (packageList != null) {
@@ -10553,7 +10598,8 @@
if (calledOnParentInstance) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
}
synchronized (getLockObject()) {
@@ -10732,7 +10778,7 @@
// Only allow the system user to use this method
Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
"createAndManageUser was called from non-system user");
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0;
@@ -10907,7 +10953,7 @@
if (mPendingUserCreatedCallbackTokens.contains(token)) {
// Ignore because it was triggered by createAndManageUser()
Slogf.d(LOG_TAG, "handleNewUserCreated(): ignoring for user " + userId
- + " due to token" + token);
+ + " due to token " + token);
mPendingUserCreatedCallbackTokens.remove(token);
return;
}
@@ -10934,10 +10980,11 @@
@Override
public void acknowledgeNewUserDisclaimer() {
CallerIdentity callerIdentity = getCallerIdentity();
- canManageUsers(callerIdentity);
+ Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
setShowNewUserDisclaimer(callerIdentity.getUserId(),
- DevicePolicyData.NEW_USER_DISCLAIMER_SHOWN);
+ DevicePolicyData.NEW_USER_DISCLAIMER_ACKNOWLEDGED);
}
private void setShowNewUserDisclaimer(@UserIdInt int userId, String value) {
@@ -10970,11 +11017,23 @@
}
@Override
+ public boolean isNewUserDisclaimerAcknowledged() {
+ CallerIdentity callerIdentity = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(callerIdentity)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
+ int userId = callerIdentity.getUserId();
+ synchronized (getLockObject()) {
+ DevicePolicyData policyData = getUserData(userId);
+ return policyData.isNewUserDisclaimerAcknowledged();
+ }
+ }
+
+ @Override
public boolean removeUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER);
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11008,7 +11067,7 @@
public boolean switchUser(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
boolean switched = false;
@@ -11026,6 +11085,8 @@
switched = mInjector.getIActivityManager().switchUser(userId);
if (!switched) {
Slogf.w(LOG_TAG, "Failed to switch to user %d", userId);
+ } else {
+ Slogf.d(LOG_TAG, "Switched");
}
return switched;
} catch (RemoteException e) {
@@ -11049,18 +11110,12 @@
}
private @UserIdInt int getLogoutUserIdUnchecked() {
- if (!mInjector.userManagerIsHeadlessSystemUserMode()) {
- // mLogoutUserId is USER_SYSTEM as well, but there's no need to acquire the lock
- return UserHandle.USER_SYSTEM;
- }
synchronized (getLockObject()) {
return mLogoutUserId;
}
}
private void clearLogoutUser() {
- if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore
-
synchronized (getLockObject()) {
setLogoutUserIdLocked(UserHandle.USER_NULL);
}
@@ -11068,8 +11123,6 @@
@GuardedBy("getLockObject()")
private void setLogoutUserIdLocked(@UserIdInt int userId) {
- if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore
-
if (userId == UserHandle.USER_CURRENT) {
userId = getCurrentForegroundUserId();
}
@@ -11083,7 +11136,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND);
final int userId = userHandle.getIdentifier();
@@ -11119,7 +11172,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER);
final int userId = userHandle.getIdentifier();
@@ -11135,7 +11188,8 @@
public int logoutUser(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER);
final int callingUserId = caller.getUserId();
@@ -11151,8 +11205,7 @@
return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
- // TODO(b/204585343): remove the headless system user check?
- if (mInjector.userManagerIsHeadlessSystemUserMode() && callingUserId != mInjector
+ if (callingUserId != mInjector
.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId())) {
Slogf.d(LOG_TAG, "logoutUser(): user %d is in background, just stopping, not switching",
callingUserId);
@@ -11165,11 +11218,18 @@
@Override
public int logoutUserInternal() {
CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- canManageUsers(caller) || hasCallingOrSelfPermission(permission.CREATE_USERS));
+ Preconditions.checkCallAuthorization(canManageUsers(caller)
+ || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
- int result = logoutUserUnchecked(getCurrentForegroundUserId());
- Slogf.d(LOG_TAG, "logout called by uid %d. Result: %d", caller.getUid(), result);
+ int currentUserId = getCurrentForegroundUserId();
+ if (VERBOSE_LOG) {
+ Slogf.v(LOG_TAG, "logout() called by uid %d; current user is %d", caller.getUid(),
+ currentUserId);
+ }
+ int result = logoutUserUnchecked(currentUserId);
+ if (VERBOSE_LOG) {
+ Slogf.v(LOG_TAG, "Result of logout(): %d", result);
+ }
return result;
}
@@ -11224,7 +11284,7 @@
public List<UserHandle> getSecondaryUsers(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers();
@@ -11244,7 +11304,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
@@ -11255,7 +11316,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11306,7 +11367,7 @@
Objects.requireNonNull(packageNames, "array of packages cannot be null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
@@ -11369,7 +11430,7 @@
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
synchronized (getLockObject()) {
@@ -11390,8 +11451,8 @@
public List<String> listPolicyExemptApps() {
CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
- || isProfileOwner(caller));
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return listPolicyExemptAppsUnchecked();
}
@@ -11432,12 +11493,19 @@
final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
getProfileOwnerOrDeviceOwnerLocked(caller), parent);
- if (isDeviceOwner(caller)) {
+ if (isDefaultDeviceOwner(caller)) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
}
Preconditions.checkArgument(!parent,
"Cannot use the parent instance in Device Owner mode");
+ } else if (isFinancedDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+ throw new SecurityException("Cannot set user restriction " + key
+ + " when managing a financed device");
+ }
+ Preconditions.checkArgument(!parent,
+ "Cannot use the parent instance in Financed Device Owner mode");
} else {
boolean profileOwnerCanChangeOnItself = !parent
&& UserRestrictionsUtils.canProfileOwnerChange(key, userHandle);
@@ -11546,7 +11614,9 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)
+ || isProfileOwner(caller)
|| (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
synchronized (getLockObject()) {
@@ -11561,7 +11631,7 @@
boolean hidden, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
List<String> exemptApps = listPolicyExemptAppsUnchecked();
@@ -11607,7 +11677,7 @@
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
@@ -11643,7 +11713,7 @@
public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
synchronized (getLockObject()) {
@@ -11687,7 +11757,7 @@
public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
int numberOfAppsInstalled = 0;
@@ -11756,7 +11826,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage()
&& isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
@@ -11872,7 +11942,8 @@
boolean uninstallBlocked) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
final int userId = caller.getUserId();
@@ -11913,7 +11984,8 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDeviceOwner(caller));
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller));
}
try {
return mIPackageManager.getBlockUninstallForUser(packageName, userId);
@@ -12087,7 +12159,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12111,7 +12184,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
@@ -12136,7 +12210,8 @@
// Check can set secondary lockscreen enabled
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
"User %d is not allowed to call setSecondaryLockscreenEnabled",
caller.getUserId());
@@ -12325,6 +12400,7 @@
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
+ enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
setLockTaskFeaturesLocked(userHandle, flags);
}
@@ -12373,6 +12449,24 @@
});
}
+ private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
+ int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+
+ if (!isFinancedDeviceOwner(caller)) {
+ return;
+ }
+
+ if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+ throw new SecurityException(
+ "Permitted lock task features when managing a financed device: "
+ + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+ + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
+ + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+ }
+ }
+
@Override
public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
@@ -12413,7 +12507,7 @@
public void setGlobalSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING)
@@ -12454,7 +12548,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkStringNotEmpty(setting, "String setting is null or empty");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
synchronized (getLockObject()) {
@@ -12476,8 +12571,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -12498,8 +12593,8 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -12510,7 +12605,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
UserHandle userHandle = caller.getUserHandle();
if (mIsAutomotive && !locationEnabled) {
@@ -12592,8 +12687,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -12612,8 +12707,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -12633,7 +12728,8 @@
public void setSecureSetting(ComponentName who, String setting, String value) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12720,7 +12816,8 @@
public void setMasterVolumeMuted(ComponentName who, boolean on) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED);
synchronized (getLockObject()) {
@@ -12737,7 +12834,8 @@
public boolean isMasterVolumeMuted(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
AudioManager audioManager =
@@ -12750,7 +12848,8 @@
public void setUserIcon(ComponentName who, Bitmap icon) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
mInjector.binderWithCleanCallingIdentity(
@@ -12766,7 +12865,8 @@
public boolean setKeyguardDisabled(ComponentName who, boolean disabled) {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -12808,7 +12908,8 @@
@Override
public boolean setStatusBarDisabled(ComponentName who, boolean disabled) {
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -13042,7 +13143,7 @@
@Override
public boolean isActiveDeviceOwner(int uid) {
- return isDeviceOwner(new CallerIdentity(uid, null, null));
+ return isDefaultDeviceOwner(new CallerIdentity(uid, null, null));
}
@Override
@@ -13633,7 +13734,7 @@
synchronized (getLockObject()) {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
if (policy == null) {
@@ -13831,7 +13932,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return mOwners.getSystemUpdateInfo();
}
@@ -13840,7 +13942,7 @@
public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
@@ -13882,11 +13984,15 @@
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE);
synchronized (getLockObject()) {
+ if (isFinancedDeviceOwner(caller)) {
+ enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
+ }
long ident = mInjector.binderClearCallingIdentity();
try {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13946,15 +14052,30 @@
}
}
+ private void enforcePermissionGrantStateOnFinancedDevice(
+ String packageName, String permission) {
+ if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) {
+ throw new SecurityException(permission + " cannot be used when managing a financed"
+ + " device for permission grant state");
+ } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) {
+ throw new SecurityException("Device owner package is the only package that can be used"
+ + " for permission grant state when managing a financed device");
+ }
+ }
+
@Override
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
+ if (isFinancedDeviceOwner(caller)) {
+ enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
+ }
return mInjector.binderWithCleanCallingIdentity(() -> {
int granted;
if (getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -14231,7 +14352,7 @@
}
private void checkIsDeviceOwner(CallerIdentity caller) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid()
+ " is not device owner");
}
@@ -14263,8 +14384,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -14299,7 +14420,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
return isManagedProfile(caller.getUserId());
}
@@ -14308,7 +14430,7 @@
public void reboot(ComponentName admin) {
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT);
mInjector.binderWithCleanCallingIdentity(() -> {
// Make sure there are no ongoing calls on the device.
@@ -14542,7 +14664,8 @@
return null;
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || canManageUsers(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName;
@@ -14576,7 +14699,8 @@
Objects.requireNonNull(who);
Objects.requireNonNull(packageNames);
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
if (!mHasFeature) {
@@ -14627,7 +14751,8 @@
return new ArrayList<>();
}
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Admin %s does not own the profile", caller.getComponentName());
synchronized (getLockObject()) {
@@ -14766,7 +14891,8 @@
final Set<String> affiliationIds = new ArraySet<>(ids);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
final int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
@@ -14797,7 +14923,8 @@
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds);
@@ -14891,7 +15018,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14928,7 +15055,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -14961,7 +15088,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15007,7 +15134,7 @@
if (admin != null) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDeviceOwner(caller));
+ || isDefaultDeviceOwner(caller));
} else {
// A delegate app passes a null admin component, which is expected
Preconditions.checkCallAuthorization(
@@ -15246,7 +15373,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
toggleBackupServiceActive(caller.getUserId(), enabled);
}
@@ -15259,7 +15387,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
@@ -15334,7 +15463,8 @@
}
Objects.requireNonNull(admin);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -15462,7 +15592,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
@@ -15622,7 +15752,7 @@
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller)
+ && (isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
|| hasCallingOrSelfPermission(permission.MANAGE_USERS));
@@ -15654,7 +15784,7 @@
final boolean isManagedProfileOwner = isProfileOwner(caller)
&& isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller) || isManagedProfileOwner))
+ && (isDefaultDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
if (mOwners.hasDeviceOwner()) {
checkAllUsersAreAffiliatedWithDevice();
@@ -15815,14 +15945,16 @@
@Override
public long getLastSecurityLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
}
@Override
public long getLastBugReportRequestTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || canManageUsers(caller));
return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
}
@@ -15830,7 +15962,7 @@
public long getLastNetworkLogRetrievalTime() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
|| canManageUsers(caller));
final int affectedUserId = getNetworkLoggingAffectedUser();
@@ -15846,7 +15978,8 @@
throw new IllegalArgumentException("token must be at least 32-byte long");
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15870,7 +16003,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int userHandle = caller.getUserId();
@@ -15895,7 +16029,8 @@
return false;
}
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
return isResetPasswordTokenActiveForUserLocked(caller.getUserId());
@@ -15920,7 +16055,8 @@
Objects.requireNonNull(token);
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(caller.getUserId());
@@ -15945,7 +16081,7 @@
@Override
public boolean isCurrentInputMethodSetByOwner() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method.");
return getUserData(caller.getUserId()).mCurrentInputMethodSet;
@@ -15956,7 +16092,7 @@
final int userId = user.getIdentifier();
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization((userId == caller.getUserId())
- || isProfileOwner(caller) || isDeviceOwner(caller)
+ || isProfileOwner(caller) || isDefaultDeviceOwner(caller)
|| hasFullCrossUsersPermission(caller, userId));
synchronized (getLockObject()) {
@@ -15973,7 +16109,8 @@
Objects.requireNonNull(callback, "callback is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA);
long ident = mInjector.binderClearCallingIdentity();
@@ -16005,7 +16142,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED);
synchronized (getLockObject()) {
@@ -16054,7 +16191,8 @@
"Provided administrator and target have the same package name.");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
final int callingUserId = caller.getUserId();
final DevicePolicyData policy = getUserData(callingUserId);
@@ -16096,7 +16234,7 @@
if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
- } else if (isDeviceOwner(caller)) {
+ } else if (isDefaultDeviceOwner(caller)) {
ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
@@ -16177,7 +16315,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String startUserSessionMessageString =
startUserSessionMessage != null ? startUserSessionMessage.toString() : null;
@@ -16202,7 +16340,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final String endUserSessionMessageString =
endUserSessionMessage != null ? endUserSessionMessage.toString() : null;
@@ -16227,7 +16365,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16242,7 +16380,7 @@
}
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -16258,7 +16396,8 @@
@Nullable
public PersistableBundle getTransferOwnershipBundle() {
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
final int callingUserId = caller.getUserId();
@@ -16288,7 +16427,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
if (tm != null) {
@@ -16309,7 +16448,7 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
if (apnId < 0) {
return false;
@@ -16331,7 +16470,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return removeOverrideApnUnchecked(apnId);
}
@@ -16352,7 +16491,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return getOverrideApnsUnchecked();
}
@@ -16373,7 +16512,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED);
setOverrideApnsEnabledUnchecked(enabled);
@@ -16393,7 +16532,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
() -> mContext.getContentResolver().query(
@@ -16476,7 +16615,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
@@ -16515,7 +16654,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
switch (currentMode) {
@@ -16537,7 +16676,7 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
}
@@ -16547,8 +16686,8 @@
Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
DevicePolicyEventLogger
@@ -16923,7 +17062,8 @@
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(packages, "packages is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
@@ -16942,7 +17082,8 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
synchronized (getLockObject()) {
return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName());
@@ -16954,7 +17095,7 @@
Objects.requireNonNull(who, "Admin component name must be provided");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
@@ -16974,7 +17115,7 @@
if (who != null) {
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Common Criteria mode can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -17446,7 +17587,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller)
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller)
|| isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
synchronized (getLockObject()) {
@@ -17467,7 +17608,7 @@
final CallerIdentity caller = getCallerIdentity(callerPackage);
// Only the DPC can set this ID.
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller),
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller),
"Only a Device Owner or Profile Owner may set the Enterprise ID.");
// Empty enterprise ID must not be provided in calls to this method.
Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
@@ -17559,6 +17700,13 @@
? Collections.emptySet()
: mOverlayPackagesProvider.getNonRequiredApps(
admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE);
+ if (nonRequiredApps.isEmpty()) {
+ Slogf.i(LOG_TAG, "No disallowed packages for the managed profile.");
+ } else {
+ for (String packageName : nonRequiredApps) {
+ Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]");
+ }
+ }
userInfo = mUserManager.createProfileForUserEvenWhenDisallowed(
provisioningParams.getProfileName(),
UserManager.USER_TYPE_PROFILE_MANAGED,
@@ -17577,6 +17725,8 @@
startTime,
callerPackage);
+ onCreateAndProvisionManagedProfileStarted(provisioningParams);
+
installExistingAdminPackage(userInfo.id, admin.getPackageName());
if (!enableAdminAndSetProfileOwner(
userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -17597,6 +17747,8 @@
}
}
+ onCreateAndProvisionManagedProfileCompleted(provisioningParams);
+
sendProvisioningCompletedBroadcast(
userInfo.id,
ACTION_PROVISION_MANAGED_PROFILE,
@@ -17618,6 +17770,29 @@
}
}
+ /**
+ * Callback called at the beginning of {@link #createAndProvisionManagedProfile(
+ * ManagedProfileProvisioningParams, String)} after the relevant prechecks have passed.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onCreateAndProvisionManagedProfileStarted(
+ ManagedProfileProvisioningParams provisioningParams) {}
+
+ /**
+ * Callback called at the end of {@link #createAndProvisionManagedProfile(
+ * ManagedProfileProvisioningParams, String)} after all the other provisioning tasks
+ * have completed successfully.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onCreateAndProvisionManagedProfileCompleted(
+ ManagedProfileProvisioningParams provisioningParams) {}
+
private void resetInteractAcrossProfilesAppOps() {
mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps();
pregrantDefaultInteractAcrossProfilesAppOps();
@@ -17857,6 +18032,7 @@
ERROR_PRE_CONDITION_FAILED,
"Provisioning preconditions failed with result: " + result);
}
+ onProvisionFullyManagedDeviceStarted(provisioningParams);
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
@@ -17882,6 +18058,7 @@
disallowAddUser();
setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+ onProvisionFullyManagedDeviceCompleted(provisioningParams);
sendProvisioningCompletedBroadcast(
deviceOwnerUserId,
ACTION_PROVISION_MANAGED_DEVICE,
@@ -17897,6 +18074,29 @@
}
}
+ /**
+ * Callback called at the beginning of {@link #provisionFullyManagedDevice(
+ * FullyManagedDeviceProvisioningParams, String)} after the relevant prechecks have passed.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onProvisionFullyManagedDeviceStarted(
+ FullyManagedDeviceProvisioningParams provisioningParams) {}
+
+ /**
+ * Callback called at the end of {@link #provisionFullyManagedDevice(
+ * FullyManagedDeviceProvisioningParams, String)} after all the other provisioning tasks
+ * have completed successfully.
+ *
+ * <p>The logic in this method blocks provisioning.
+ *
+ * <p>This method is meant to be overridden by OEMs.
+ */
+ private void onProvisionFullyManagedDeviceCompleted(
+ FullyManagedDeviceProvisioningParams provisioningParams) {}
+
private void setTimeAndTimezone(String timeZone, long localTime) {
try {
final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
@@ -18141,27 +18341,55 @@
@DeviceOwnerType int deviceOwnerType) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- verifyDeviceOwnerTypePreconditions(admin);
-
- final String packageName = admin.getPackageName();
- Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
- "The device owner type has already been set for " + packageName);
synchronized (getLockObject()) {
- mOwners.setDeviceOwnerType(packageName, deviceOwnerType);
+ setDeviceOwnerTypeLocked(admin, deviceOwnerType);
}
}
+ private void setDeviceOwnerTypeLocked(ComponentName admin,
+ @DeviceOwnerType int deviceOwnerType) {
+ String packageName = admin.getPackageName();
+ boolean isAdminTestOnly;
+
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
+
+ isAdminTestOnly = isAdminTestOnlyLocked(admin, mOwners.getDeviceOwnerUserId());
+ Preconditions.checkState(isAdminTestOnly
+ || !mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName),
+ "Test only admins can only set the device owner type more than once");
+
+ mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly);
+ }
+
@Override
@DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) {
- verifyDeviceOwnerTypePreconditions(admin);
synchronized (getLockObject()) {
- return mOwners.getDeviceOwnerType(admin.getPackageName());
+ verifyDeviceOwnerTypePreconditionsLocked(admin);
+ return getDeviceOwnerTypeLocked(admin.getPackageName());
}
}
- private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) {
+ @DeviceOwnerType
+ private int getDeviceOwnerTypeLocked(String packageName) {
+ return mOwners.getDeviceOwnerType(packageName);
+ }
+
+ /**
+ * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner
+ * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for
+ * the case where the caller is not the device owner, there is no device owner, or the device
+ * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}.
+ */
+ private boolean isFinancedDeviceOwner(CallerIdentity caller) {
+ synchronized (getLockObject()) {
+ return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked(
+ mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED;
+ }
+ }
+
+ private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) {
Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner");
Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin),
"admin is not the device owner");
@@ -18172,7 +18400,7 @@
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
Preconditions.checkState(canUsbDataSignalingBeDisabled(),
@@ -18213,7 +18441,7 @@
synchronized (getLockObject()) {
// If the caller is an admin, return the policy set by itself. Otherwise
// return the device-wide policy.
- if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) {
return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled;
} else {
return isUsbDataSignalingEnabledInternalLocked();
@@ -18254,7 +18482,7 @@
public void setMinimumRequiredWifiSecurityLevel(int level) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"Wi-Fi minimum security level can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18284,7 +18512,7 @@
public void setSsidAllowlist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID allowlist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18306,10 +18534,11 @@
public List<String> getSsidAllowlist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
"SSID allowlist can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or a system app.");
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
@@ -18322,7 +18551,7 @@
public void setSsidDenylist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID denylist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
@@ -18344,10 +18573,11 @@
public List<String> getSsidDenylist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller),
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
"SSID denylist can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or a system app.");
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
@@ -18446,4 +18676,26 @@
mContext.sendBroadcastAsUser(intent, user);
}
}
+
+ public boolean isDpcDownloaded() {
+ Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ ContentResolver cr = mContext.getContentResolver();
+
+ return mInjector.binderWithCleanCallingIdentity(() -> Settings.Secure.getIntForUser(
+ cr, MANAGED_PROVISIONING_DPC_DOWNLOADED,
+ /* def= */ 0, /* userHandle= */ cr.getUserId())
+ == 1);
+ }
+
+ public void setDpcDownloaded(boolean downloaded) {
+ Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ int setTo = downloaded ? 1 : 0;
+
+ mInjector.binderWithCleanCallingIdentity(() -> Settings.Secure.putInt(
+ mContext.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, setTo));
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 685cf05..598f9e8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -34,6 +34,7 @@
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -41,6 +42,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.view.inputmethod.InputMethodInfo;
@@ -91,6 +93,8 @@
List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
String getActiveApexPackageNameContainingPackage(String packageName);
+
+ String getDeviceManagerRoleHolderPackageName(Context context);
}
private static final class DefaultInjector implements Injector {
@@ -104,6 +108,19 @@
public String getActiveApexPackageNameContainingPackage(String packageName) {
return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName);
}
+
+ @Override
+ public String getDeviceManagerRoleHolderPackageName(Context context) {
+ return Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ List<String> roleHolders =
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER);
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
}
@VisibleForTesting
@@ -142,9 +159,20 @@
nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
nonRequiredApps.removeAll(
getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction));
+ nonRequiredApps.removeAll(getDeviceManagerRoleHolders());
return nonRequiredApps;
}
+ private Set<String> getDeviceManagerRoleHolders() {
+ HashSet<String> result = new HashSet<>();
+ String deviceManagerRoleHolderPackageName =
+ mInjector.getDeviceManagerRoleHolderPackageName(mContext);
+ if (deviceManagerRoleHolderPackageName != null) {
+ result.add(deviceManagerRoleHolderPackageName);
+ }
+ return result;
+ }
+
/**
* Returns a subset of {@code packageNames} whose packages are mainline modules declared as
* required apps via their app metadata.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3584728..fe8f223 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -637,13 +637,15 @@
}
}
- void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType) {
+ void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType,
+ boolean isAdminTestOnly) {
synchronized (mLock) {
if (!hasDeviceOwner()) {
Slog.e(TAG, "Attempting to set a device owner type when there is no device owner");
return;
- } else if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
- Slog.e(TAG, "Device owner type for " + packageName + " has already been set");
+ } else if (!isAdminTestOnly && isDeviceOwnerTypeSetForDeviceOwner(packageName)) {
+ Slog.e(TAG, "Setting the device owner type more than once is only allowed"
+ + " for test only admins");
return;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1fe71f8..a111831 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
+import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -176,6 +177,7 @@
import com.android.server.powerstats.PowerStatsService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
+import com.android.server.resources.ResourcesManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleServicePlatformHelper;
import com.android.server.rotationresolver.RotationResolverManagerService;
@@ -183,6 +185,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;
@@ -222,8 +225,8 @@
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -264,8 +267,6 @@
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
- private static final String NEARBY_SERVICE_APEX_PATH =
- "/apex/com.android.nearby/javalib/service-nearby.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -276,8 +277,6 @@
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
"com.android.server.midi.MidiService$Lifecycle";
- private static final String NEARBY_SERVICE_CLASS =
- "com.android.server.nearby.NearbyService";
private static final String WIFI_APEX_SERVICE_JAR_PATH =
"/apex/com.android.wifi/javalib/service-wifi.jar";
private static final String WIFI_SERVICE_CLASS =
@@ -368,6 +367,8 @@
"com.android.server.adb.AdbService$Lifecycle";
private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
"com.android.server.speech.SpeechRecognitionManagerService";
+ private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
+ "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
"com.android.server.appprediction.AppPredictionManagerService";
private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
@@ -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 =
@@ -1286,6 +1289,13 @@
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext));
t.traceEnd();
+ // Manages Resources packages
+ t.traceBegin("StartResourcesManagerService");
+ ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext);
+ resourcesService.setActivityManagerService(mActivityManagerService);
+ mSystemServiceManager.startService(resourcesService);
+ t.traceEnd();
+
t.traceBegin("StartSensorPrivacyService");
mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
t.traceEnd();
@@ -1383,7 +1393,6 @@
DynamicSystemService dynamicSystem = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
- IpSecService ipSecService = null;
VpnManagerService vpnManager = null;
VcnManagementService vcnManagement = null;
NetworkStatsService networkStats = null;
@@ -1452,6 +1461,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();
@@ -1459,7 +1472,7 @@
// TelecomLoader hooks into classes with defined HFP logic,
// so check for either telephony or microphone.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
@@ -1467,7 +1480,7 @@
t.traceBegin("StartTelephonyRegistry");
telephonyRegistry = new TelephonyRegistry(
- context, new TelephonyRegistry.ConfigurationProvider());
+ context, new TelephonyRegistry.ConfigurationProvider());
ServiceManager.addService("telephony.registry", telephonyRegistry);
t.traceEnd();
@@ -1853,6 +1866,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);
@@ -1878,16 +1897,6 @@
}
t.traceEnd();
-
- t.traceBegin("StartIpSecService");
- try {
- ipSecService = IpSecService.create(context);
- ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
- } catch (Throwable e) {
- reportWtf("starting IpSec Service", e);
- }
- t.traceEnd();
-
t.traceBegin("StartFontManagerService");
mSystemServiceManager.startService(new FontManagerService.Lifecycle(context, safeMode));
t.traceEnd();
@@ -1987,16 +1996,6 @@
}
t.traceEnd();
- // Start Nearby Service.
- t.traceBegin("StartNearbyService");
- try {
- mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS,
- NEARBY_SERVICE_APEX_PATH);
- } catch (Throwable e) {
- reportWtf("starting NearbyService", e);
- }
- t.traceEnd();
-
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
// and NetworkPolicyManager because ConnectivityService needs to take these
@@ -2119,6 +2118,14 @@
Slog.i(TAG, "Wallpaper service disabled by config");
}
+ // WallpaperEffectsGeneration manager service
+ // TODO (b/135218095): Use deviceHasConfigString(context,
+ // R.string.config_defaultWallpaperEffectsGenerationService)
+ t.traceBegin("StartWallpaperEffectsGenerationService");
+ mSystemServiceManager.startService(
+ WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("StartAudioService");
if (!isArc) {
mSystemServiceManager.startService(AudioService.Lifecycle.class);
@@ -2776,7 +2783,6 @@
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
- final IpSecService ipSecServiceF = ipSecService;
final VpnManagerService vpnManagerF = vpnManager;
final VcnManagementService vcnManagementF = vcnManagement;
final WindowManagerService windowManagerF = wm;
@@ -2868,15 +2874,6 @@
.networkScoreAndNetworkManagementServiceReady();
}
t.traceEnd();
- t.traceBegin("MakeIpSecServiceReady");
- try {
- if (ipSecServiceF != null) {
- ipSecServiceF.systemReady();
- }
- } catch (Throwable e) {
- reportWtf("making IpSec Service ready", e);
- }
- t.traceEnd();
t.traceBegin("MakeNetworkStatsServiceReady");
try {
if (networkStatsF != null) {
@@ -3001,7 +2998,9 @@
t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
- if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+ if (telephonyRegistryF != null) {
+ telephonyRegistryF.systemRunning();
+ }
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
@@ -3066,10 +3065,12 @@
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("startApexServices");
- Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
- // TODO(satayev): introduce android:order for services coming the same apexes
- for (String name : new TreeSet<>(services.keySet())) {
- String jarPath = services.get(name);
+ // TODO(b/192880996): get the list from "android" package, once the manifest entries
+ // are migrated to system manifest.
+ List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
+ for (ApexSystemServiceInfo info : services) {
+ String name = info.getName();
+ String jarPath = info.getJarPath();
t.traceBegin("starting " + name);
if (TextUtils.isEmpty(jarPath)) {
mSystemServiceManager.startService(name);
@@ -3170,11 +3171,6 @@
}
private void startAmbientContextService(@NonNull TimingsTraceAndSlog t) {
- if (!AmbientContextManagerService.isDetectionServiceConfigured()) {
- Slog.d(TAG, "AmbientContextDetectionService is not configured on this device");
- return;
- }
-
t.traceBegin("StartAmbientContextService");
mSystemServiceManager.startService(AmbientContextManagerService.class);
t.traceEnd();
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index d562786..717168f 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -90,6 +90,11 @@
private static final String TAG = "MidiService";
+ // These limits are much higher than any normal app should need.
+ private static final int MAX_DEVICE_SERVERS_PER_UID = 16;
+ private static final int MAX_LISTENERS_PER_CLIENT = 16;
+ private static final int MAX_CONNECTIONS_PER_CLIENT = 64;
+
private final Context mContext;
// list of all our clients, keyed by Binder token
@@ -161,6 +166,10 @@
}
public void addListener(IMidiDeviceListener listener) {
+ if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
+ throw new SecurityException(
+ "too many MIDI listeners for UID = " + mUid);
+ }
// Use asBinder() so that we can match it in removeListener().
// The listener proxy objects themselves do not match.
mListeners.put(listener.asBinder(), listener);
@@ -174,6 +183,10 @@
}
public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
+ if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
+ throw new SecurityException(
+ "too many MIDI connections for UID = " + mUid);
+ }
DeviceConnection connection = new DeviceConnection(device, this, callback);
mDeviceConnections.put(connection.getToken(), connection);
device.addDeviceConnection(connection);
@@ -902,6 +915,19 @@
IMidiDeviceServer server, ServiceInfo serviceInfo,
boolean isPrivate, int uid, int defaultProtocol) {
+ // Limit the number of devices per app.
+ int deviceCountForApp = 0;
+ for (Device device : mDevicesByInfo.values()) {
+ if (device.getUid() == uid) {
+ deviceCountForApp++;
+ }
+ }
+ if (deviceCountForApp >= MAX_DEVICE_SERVERS_PER_UID) {
+ throw new SecurityException(
+ "too many MIDI devices already created for UID = "
+ + uid);
+ }
+
int id = mNextDeviceId++;
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
inputPortNames, outputPortNames, properties, isPrivate,
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/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 5220c8f..c5709fc 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -19,6 +19,7 @@
import android.app.PropertyInvalidatedCache
import android.content.ComponentName
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.android.server.pm.pkg.component.ParsedActivity
import android.os.Binder
@@ -167,12 +168,12 @@
@Parameterized.Parameter(0)
lateinit var params: Params
- private lateinit var testHandler: TestHandler
private lateinit var mockPendingBroadcasts: PendingPackageBroadcasts
private lateinit var mockPkg: AndroidPackage
private lateinit var mockPkgSetting: PackageSetting
private lateinit var service: PackageManagerService
+ private val testHandler = TestHandler(null)
private val userId = UserHandle.getCallingUserId()
private val userIdDifferent = userId + 1
@@ -180,16 +181,16 @@
fun setUpMocks() {
makeTestData()
- testHandler = TestHandler(null)
+ mockPendingBroadcasts = PendingPackageBroadcasts()
+ service = mockService()
+
+ testHandler.clear()
+
if (params.result is Result.ChangedWithoutNotify) {
// Case where the handler already has a message and so another should not be sent.
// This case will verify that only 1 message exists, which is the one added here.
testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST)
}
-
- mockPendingBroadcasts = PendingPackageBroadcasts()
-
- service = mockService()
}
@Test
@@ -201,19 +202,22 @@
when (val result = params.result) {
Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> {
runUpdate()
- verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!,
- TEST_LABEL, TEST_ICON, userId)
+ mockPkgSetting.getUserStateOrDefault(userId)
+ .getOverrideLabelIconForComponent(params.componentName!!)
+ .let {
+ assertThat(it?.first).isEqualTo(TEST_LABEL)
+ assertThat(it?.second).isEqualTo(TEST_ICON)
+ }
}
is Result.Exception -> {
assertThrows(result.type) { runUpdate() }
- verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon(
- any<ComponentName>(), any(), anyInt(), anyInt())
}
}
}
@After
fun verifyExpectedResult() {
+ assertServiceInitialized() ?: return
if (params.componentName != null) {
val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
if (activityInfo != null) {
@@ -225,11 +229,14 @@
@After
fun verifyDifferentUserUnchanged() {
+ assertServiceInitialized() ?: return
when (params.result) {
Result.Changed, Result.ChangedWithoutNotify -> {
- val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent)
- assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
- assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON)
+ // Suppress so that failures in @After don't override the actual test failure
+ @Suppress("UNNECESSARY_SAFE_CALL")
+ val activityInfo = service?.getActivityInfo(params.componentName, 0, userIdDifferent)
+ assertThat(activityInfo?.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
+ assertThat(activityInfo?.icon).isEqualTo(DEFAULT_ICON)
}
Result.NotChanged, is Result.Exception -> {}
}.run { /*exhaust*/ }
@@ -237,6 +244,7 @@
@After
fun verifyHandlerHasMessage() {
+ assertServiceInitialized() ?: return
when (params.result) {
is Result.Changed, is Result.ChangedWithoutNotify -> {
assertThat(testHandler.pendingMessages).hasSize(1)
@@ -251,9 +259,10 @@
@After
fun verifyPendingBroadcast() {
+ assertServiceInitialized() ?: return
when (params.result) {
is Result.Changed, Result.ChangedWithoutNotify -> {
- assertThat(mockPendingBroadcasts.get(userId, params.pkgName))
+ assertThat(mockPendingBroadcasts.get(userId, params.pkgName) ?: emptyList<String>())
.containsExactly(params.componentName!!.className)
.inOrder()
}
@@ -271,26 +280,27 @@
.apply(block)
.hideAsFinal()
- private fun makePkgSetting(pkgName: String) = spy(
+ private fun makePkgSetting(pkgName: String, pkg: AndroidPackage) =
PackageSetting(
pkgName, null, File("/test"),
null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
- )
- ) {
- this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
- }
+ ).apply {
+ if (params.isSystem) {
+ this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
+ }
+ this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
+ this.pkg = pkg
+ }
private fun makeTestData() {
mockPkg = makePkg(params.pkgName)
- mockPkgSetting = makePkgSetting(params.pkgName)
+ mockPkgSetting = makePkgSetting(params.pkgName, mockPkg)
if (params.result is Result.NotChanged) {
// If verifying no-op behavior, set the current setting to the test values
mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
TEST_ICON, userId)
- // Then clear the mock because the line above just incremented it
- clearInvocations(mockPkgSetting)
}
}
@@ -303,9 +313,9 @@
INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
)
val mockedPkgSettings = mutableMapOf(
- VALID_PKG to makePkgSetting(VALID_PKG),
- SHARED_PKG to makePkgSetting(SHARED_PKG),
- INVALID_PKG to makePkgSetting(INVALID_PKG)
+ VALID_PKG to makePkgSetting(VALID_PKG, mockedPkgs[VALID_PKG]!!),
+ SHARED_PKG to makePkgSetting(SHARED_PKG, mockedPkgs[SHARED_PKG]!!),
+ INVALID_PKG to makePkgSetting(INVALID_PKG, mockedPkgs[INVALID_PKG]!!)
)
var mockActivity: ParsedActivity? = null
@@ -330,6 +340,8 @@
whenever(this.componentExists(same(it))) { mockActivity != null }
whenever(this.getActivity(same(it))) { mockActivity }
}
+ whenever(this.snapshot()) { this@mockThrowOnUnmocked }
+ whenever(registerObserver(any())).thenCallRealMethod()
}
val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
@@ -345,6 +357,8 @@
val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
any<PackageSetting>(), anyInt())) { false }
+ whenever(this.snapshot()) { this@mockThrowOnUnmocked }
+ whenever(registerObserver(any())).thenCallRealMethod()
}
val mockContext: Context = mockThrowOnUnmocked {
whenever(this.getString(
@@ -354,27 +368,33 @@
PackageManager.PERMISSION_GRANTED
}
}
- val mockSharedLibrariesImpl: SharedLibrariesImpl = mock()
+ val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
+ whenever(this.snapshot()) { this@mock }
+ }
val mockInjector: PackageManagerServiceInjector = mock {
whenever(this.lock) { PackageManagerTracedLock() }
whenever(this.componentResolver) { mockComponentResolver }
whenever(this.userManagerService) { mockUserManagerService }
- whenever(this.getUserManagerInternal()) { mockUserManagerInternal }
+ whenever(this.userManagerInternal) { mockUserManagerInternal }
whenever(this.settings) { mockSettings }
whenever(this.getLocalService(ActivityTaskManagerInternal::class.java)) {
mockActivityTaskManager
}
whenever(this.appsFilter) { mockAppsFilter }
whenever(this.context) { mockContext }
- whenever(this.getHandler()) { testHandler }
+ whenever(this.handler) { testHandler }
whenever(this.sharedLibrariesImpl) { mockSharedLibrariesImpl }
}
val testParams = PackageManagerServiceTestParams().apply {
this.pendingPackageBroadcasts = mockPendingBroadcasts
this.resolveComponentName = ComponentName("android", ".Test")
this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
+ this.instantAppRegistry = mock()
}
return PackageManagerService(mockInjector, testParams)
}
+
+ // If service isn't initialized, then test setup failed and @Afters should be skipped
+ private fun assertServiceInitialized() = Unit.takeIf { ::service.isInitialized }
}
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
index 16d6241..0a9b7b1 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp
@@ -32,7 +32,7 @@
name: "test_com.android.server",
manifest: "manifest.json",
androidManifest: "AndroidManifest.xml",
- java_libs: ["FakeApexSystemService"],
+ java_libs: ["FakeApexSystemServices"],
file_contexts: ":apex.test-file_contexts",
key: "test_com.android.server.key",
updatable: false,
diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
index eb741ca..6bec284 100644
--- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
+++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml
@@ -21,21 +21,29 @@
<application android:hasCode="false" android:testOnly="true">
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService"
- android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
- android:minSdkVersion="30"/>
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ />
+
+ <apex-system-service
+ android:name="com.android.server.testing.FakeApexSystemService2"
+ android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
+ android:minSdkVersion="30"
+ android:initOrder="1"
+ />
<!-- Always inactive system service, since maxSdkVersion is low -->
<apex-system-service
- android:name="com.android.apex.test.OldApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.OldApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="1"
android:maxSdkVersion="1"
/>
<!-- Always inactive system service, since minSdkVersion is high -->
<apex-system-service
- android:name="com.android.apex.test.NewApexSystemService"
- android:path="/apex/com.android.apex.test/javalib/fake.jar"
+ android:name="com.android.server.testing.NewApexSystemService"
+ android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="999999"
/>
</application>
diff --git a/services/tests/apexsystemservices/service/Android.bp b/services/tests/apexsystemservices/services/Android.bp
similarity index 94%
rename from services/tests/apexsystemservices/service/Android.bp
rename to services/tests/apexsystemservices/services/Android.bp
index 9d04f39..477ea4c 100644
--- a/services/tests/apexsystemservices/service/Android.bp
+++ b/services/tests/apexsystemservices/services/Android.bp
@@ -8,7 +8,7 @@
}
java_library {
- name: "FakeApexSystemService",
+ name: "FakeApexSystemServices",
srcs: ["**/*.java"],
sdk_version: "system_server_current",
libs: [
diff --git a/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
similarity index 100%
rename from services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java
rename to services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java
diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
new file mode 100644
index 0000000..e83343b
--- /dev/null
+++ b/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java
@@ -0,0 +1,41 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.SystemService;
+
+/**
+ * A fake system service that just logs when it is started.
+ */
+public class FakeApexSystemService2 extends SystemService {
+
+ private static final String TAG = "FakeApexSystemService";
+
+ public FakeApexSystemService2(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "FakeApexSystemService2 onStart");
+ }
+}
diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
index 2b453a9..10635a1 100644
--- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
+++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java
@@ -37,9 +37,15 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
+ private static final int REBOOT_TIMEOUT = 1 * 60 * 1000;
+
private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
@@ -67,7 +73,7 @@
}
@Test
- public void noApexSystemServerStartsWithoutApex() throws Exception {
+ public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat())
@@ -75,20 +81,55 @@
}
@Test
- public void apexSystemServerStarts() throws Exception {
+ public void testApexSystemServiceStarts() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
// Reboot activates the apex
mPreparer.reboot();
+ mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
assertThat(getFakeApexSystemServiceLogcat())
.contains("FakeApexSystemService onStart");
}
+ @Test
+ public void testInitOrder() throws Exception {
+ // Pre-install the apex
+ String apex = "test_com.android.server.apex";
+ mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
+ // Reboot activates the apex
+ mPreparer.reboot();
+
+ mDevice.waitForBootComplete(REBOOT_TIMEOUT);
+
+ assertThat(getFakeApexSystemServiceLogcat().lines()
+ .map(ApexSystemServicesTestCases::getDebugMessage)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()))
+ .containsExactly(
+ // Second service has a higher initOrder and must be started first
+ "FakeApexSystemService2 onStart",
+ "FakeApexSystemService onStart"
+ )
+ .inOrder();
+ }
+
private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
"*:S");
}
+ private static final Pattern DEBUG_MESSAGE =
+ Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
+
+ private static String getDebugMessage(String logcatLine) {
+ return DEBUG_MESSAGE.matcher(logcatLine)
+ .results()
+ .map(m -> m.group(1))
+ .findFirst()
+ .orElse(null);
+ }
+
}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 36246e5..75669d5 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -52,6 +52,7 @@
"service-blobstore",
"service-jobscheduler",
"service-permission.impl",
+ "service-supplementalprocess.impl",
"services.core",
"services.devicepolicy",
"services.net",
@@ -62,12 +63,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/res/xml/gama_manager_service_metadata_config_disabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
new file mode 100644
index 0000000..eb15451
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_disabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="false"
+ android:supportsBatteryGameMode="false"
+ android:allowGameAngleDriver="false"
+ android:allowGameDownscaling="false"
+ android:allowGameFpsOverride="false"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
new file mode 100644
index 0000000..65b7467
--- /dev/null
+++ b/services/tests/mockingservicestests/res/xml/gama_manager_service_metadata_config_enabled.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<game-mode-config
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsPerformanceGameMode="false"
+ android:supportsBatteryGameMode="false"
+ android:allowGameAngleDriver="true"
+ android:allowGameDownscaling="true"
+ android:allowGameFpsOverride="true"
+/>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
index fec9b12..e89c812 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
@@ -359,6 +359,34 @@
LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
}
+ @Test
+ public void dispatchTransientVisibilityChanged_valueUnchanged_doesNotInvokeCallback() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures).hasSize(0);
+ }
+
+ @Test
+ public void dispatchTransientVisibilityChanged_valueChanged_invokesCallback() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+ .containsExactly(true).inOrder();
+ }
+
+ @Test
+ public void dispatchTransientVisibilityChanged_manyTimes_invokesCallbackWhenValueChanges() {
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+ mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true);
+
+ assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures)
+ .containsExactly(true, false, true).inOrder();
+ }
+
private static class LifecycleTrackingGameSession extends GameSession {
private enum LifecycleMethodCall {
ON_CREATE,
@@ -368,6 +396,8 @@
}
final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>();
+ final List<Boolean> mCapturedTransientSystemBarVisibilityFromRevealGestures =
+ new ArrayList<>();
@Override
public void onCreate() {
@@ -387,5 +417,11 @@
mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED);
}
}
+
+ @Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(
+ boolean visibleDueToGesture) {
+ mCapturedTransientSystemBarVisibilityFromRevealGestures.add(visibleDueToGesture);
+ }
}
}
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..7670953
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -0,0 +1,2448 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.telephony.TelephonyManager;
+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;
+ @Mock private TelephonyManager mTelephonyManager;
+
+ 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
+ TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ @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/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 303f955..bf46f55 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -97,7 +97,7 @@
@Before
public void setUp() {
- System.loadLibrary("activitymanagermockingservicestestjni");
+ System.loadLibrary("mockingservicestestjni");
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
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..d2358a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -23,19 +23,32 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.GameManager;
import android.app.GameModeInfo;
+import android.app.GameState;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.hardware.power.Mode;
import android.os.Bundle;
+import android.os.PowerManagerInternal;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -45,6 +58,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +89,8 @@
private TestLooper mTestLooper;
@Mock
private PackageManager mMockPackageManager;
+ @Mock
+ private PowerManagerInternal mMockPowerManager;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -146,19 +164,27 @@
mPackageName = mMockContext.getPackageName();
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ applicationInfo.packageName = mPackageName;
final PackageInfo pi = new PackageInfo();
pi.packageName = mPackageName;
pi.applicationInfo = applicationInfo;
final List<PackageInfo> packages = new ArrayList<>();
packages.add(pi);
+
+ final Resources resources =
+ InstrumentationRegistry.getInstrumentation().getContext().getResources();
+ when(mMockPackageManager.getResourcesForApplication(anyString()))
+ .thenReturn(resources);
when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
.thenReturn(packages);
when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
+ LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
@After
public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.disableCompatScale(mPackageName);
if (mMockingSession != null) {
@@ -167,7 +193,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();
}
@@ -306,6 +333,46 @@
.thenReturn(applicationInfo);
}
+ private void mockInterventionsEnabledFromXml() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ final int resId = 123;
+ metaDataBundle.putInt(
+ GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+ "res/xml/gama_manager_service_metadata_config_enabled.xml");
+ }
+
+ private void mockInterventionsDisabledFromXml() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ final int resId = 123;
+ metaDataBundle.putInt(
+ GameManagerService.GamePackageConfiguration.METADATA_GAME_MODE_CONFIG, resId);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ seedGameManagerServiceMetaDataFromFile(mPackageName, resId,
+ "res/xml/gama_manager_service_metadata_config_disabled.xml");
+ }
+
+
+ private void seedGameManagerServiceMetaDataFromFile(String packageName, int resId,
+ String fileName)
+ throws Exception {
+ AssetManager assetManager =
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+ XmlResourceParser xmlResourceParser =
+ assetManager.openXmlResourceParser(fileName);
+ when(mMockPackageManager.getXml(eq(packageName), eq(resId), any()))
+ .thenReturn(xmlResourceParser);
+ }
+
/**
* By default game mode is not supported.
*/
@@ -495,8 +562,8 @@
gameManagerService.getConfig(mPackageName);
assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
- // Validate GameManagerService.getAngleEnabled() returns the correct value.
- assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+ // Validate GameManagerService.isAngleEnabled() returns the correct value.
+ assertEquals(gameManagerService.isAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
}
private void checkFps(GameManagerService gameManagerService, int gameMode, int fps) {
@@ -507,7 +574,7 @@
}
GameManagerService.GamePackageConfiguration config =
gameManagerService.getConfig(mPackageName);
- assertEquals(config.getGameModeConfiguration(gameMode).getFps(), fps);
+ assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
/**
@@ -861,7 +928,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();
@@ -889,6 +956,36 @@
}
@Test
+ public void testGameModeConfigAllowFpsTrue() throws Exception {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ mockInterventionsEnabledFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(90,
+ config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+ assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+ }
+
+ @Test
+ public void testGameModeConfigAllowFpsFalse() throws Exception {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ mockInterventionsDisabledFromXml();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(0,
+ config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
+ assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
+ }
+
+ @Test
public void testInterventionFps() throws Exception {
mockDeviceConfigAll();
mockModifyGameModeGranted();
@@ -1048,4 +1145,41 @@
assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
assertEquals(0, gameModeInfo.getAvailableGameModes().length);
}
+
+ @Test
+ public void testGameStateLoadingRequiresPerformanceMode() {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ GameState gameState = new GameState(true, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ }
+
+ private void setGameState(boolean isLoading) {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext, mTestLooper.getLooper());
+ startUser(gameManagerService, USER_ID_1);
+ gameManagerService.setGameMode(
+ mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ GameState gameState = new GameState(isLoading, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+ }
+
+ @Test
+ public void testSetGameStateLoading() {
+ setGameState(true);
+ }
+
+ @Test
+ public void testSetGameStateNotLoading() {
+ setGameState(false);
+ }
}
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/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index d5e4710..7f57119 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -26,13 +26,14 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -59,6 +60,7 @@
import android.service.games.IGameSession;
import android.service.games.IGameSessionController;
import android.service.games.IGameSessionService;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
import androidx.test.filters.SmallTest;
@@ -70,6 +72,7 @@
import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
import com.android.internal.util.Preconditions;
import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
import com.android.server.wm.WindowManagerService;
import org.junit.After;
@@ -83,6 +86,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Objects;
/**
@@ -109,6 +113,7 @@
private static final ComponentName GAME_B_MAIN_ACTIVITY =
new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity");
+
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
private MockitoSession mMockingSession;
@@ -121,13 +126,14 @@
private WindowManagerInternal mMockWindowManagerInternal;
@Mock
private IActivityManager mMockActivityManager;
- private FakeContext mFakeContext;
+ private MockContext mMockContext;
private FakeGameClassifier mFakeGameClassifier;
private FakeGameService mFakeGameService;
private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
private FakeGameSessionService mFakeGameSessionService;
private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
private ArrayList<ITaskStackListener> mTaskStackListeners;
+ private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners;
private ArrayList<RunningTaskInfo> mRunningTaskInfos;
@Mock
@@ -140,7 +146,7 @@
.strictness(Strictness.LENIENT)
.startMocking();
- mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext());
+ mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext());
mFakeGameClassifier = new FakeGameClassifier();
mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
@@ -156,20 +162,30 @@
mTaskStackListeners.add(invocation.getArgument(0));
return null;
}).when(mMockActivityTaskManager).registerTaskStackListener(any());
-
- mRunningTaskInfos = new ArrayList<>();
- when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
- mRunningTaskInfos);
-
doAnswer(invocation -> {
mTaskStackListeners.remove(invocation.getArgument(0));
return null;
}).when(mMockActivityTaskManager).unregisterTaskStackListener(any());
+ mTaskSystemBarsListeners = new ArrayList<>();
+ doAnswer(invocation -> {
+ mTaskSystemBarsListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mMockWindowManagerInternal).registerTaskSystemBarsListener(any());
+ doAnswer(invocation -> {
+ mTaskSystemBarsListeners.remove(invocation.getArgument(0));
+ return null;
+ }).when(mMockWindowManagerInternal).unregisterTaskSystemBarsListener(any());
+
+ mRunningTaskInfos = new ArrayList<>();
+ when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
+ mRunningTaskInfos);
+
+
mGameServiceProviderInstance = new GameServiceProviderInstanceImpl(
new UserHandle(USER_ID),
ConcurrentUtils.DIRECT_EXECUTOR,
- mFakeContext,
+ mMockContext,
mFakeGameClassifier,
mMockActivityManager,
mMockActivityTaskManager,
@@ -301,6 +317,7 @@
throws Exception {
mGameServiceProviderInstance.start();
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -322,6 +339,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
@@ -336,6 +354,7 @@
mGameServiceProviderInstance.start();
dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
@@ -345,6 +364,7 @@
public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -362,7 +382,9 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
@@ -376,6 +398,7 @@
public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -384,13 +407,67 @@
.complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
- verifyNoMoreInteractions(mMockWindowManagerInternal);
+ }
+
+ @Test
+ public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() {
+ mGameServiceProviderInstance.start();
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ false,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+ });
+ }
+
+ @Test
+ public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ true,
+ /* wereRevealedFromSwipeOnSystemBar= */ true);
+ });
+
+ assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isTrue();
+ }
+
+ @Test
+ public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+ dispatchTaskSystemBarsEvent(taskSystemBarsListener -> {
+ taskSystemBarsListener.onTransientSystemBarsVisibilityChanged(
+ 10,
+ /* areVisible= */ true,
+ /* wereRevealedFromSwipeOnSystemBar= */ false);
+ });
+
+ assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isFalse();
}
@Test
public void gameTaskFocused_propagatedToGameSession() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -416,6 +493,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -432,6 +510,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
dispatchTaskRemoved(10);
@@ -449,6 +528,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -466,6 +546,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -477,7 +558,6 @@
verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10));
- verifyNoMoreInteractions(mMockWindowManagerInternal);
}
@Test
@@ -486,6 +566,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -513,6 +594,7 @@
startTask(10, GAME_A_MAIN_ACTIVITY);
startTask(11, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -530,6 +612,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -557,6 +640,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -586,6 +670,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -619,10 +704,19 @@
}
@Test
+ public void createGameSession_failurePermissionDenied() throws Exception {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10));
+ }
+
+ @Test
public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -650,8 +744,14 @@
public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
+
IGameSessionController gameSessionController = getOnlyElement(
mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
@@ -660,17 +760,28 @@
GameScreenshotResult result = resultFuture.get();
assertEquals(GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR,
result.getStatus());
- verify(mMockWindowManagerService).captureTaskBitmap(10);
+
+ verify(mMockWindowManagerService).captureTaskBitmap(eq(10), any());
}
@Test
public void takeScreenshot_success() throws Exception {
- when(mMockWindowManagerService.captureTaskBitmap(10)).thenReturn(TEST_BITMAP);
+ SurfaceControl mockOverlaySurfaceControl = Mockito.mock(SurfaceControl.class);
+ SurfaceControl[] excludeLayers = new SurfaceControl[1];
+ excludeLayers[0] = mockOverlaySurfaceControl;
+ when(mMockWindowManagerService.captureTaskBitmap(eq(10), any())).thenReturn(TEST_BITMAP);
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
+ FakeGameSession gameSession10 = new FakeGameSession();
+ SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class);
+ when(mockOverlaySurfacePackage.getSurfaceControl()).thenReturn(mockOverlaySurfaceControl);
+ mFakeGameSessionService.removePendingFutureForTaskId(10)
+ .complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage));
+
IGameSessionController gameSessionController = getOnlyElement(
mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
@@ -683,6 +794,7 @@
@Test
public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception {
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE))
@@ -691,6 +803,7 @@
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -710,14 +823,16 @@
.mGameSessionController.restartGame(10);
verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT);
- assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent);
+ assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent);
}
@Test
public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception {
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mGameServiceProviderInstance.start();
startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
mFakeGameService.requestCreateGameSession(10);
FakeGameSession gameSession10 = new FakeGameSession();
@@ -730,7 +845,20 @@
.mGameSessionController.restartGame(11);
verifyZeroInteractions(mMockActivityManager);
- assertThat(mFakeContext.getLastStartedIntent()).isNull();
+ assertThat(mMockContext.getLastStartedIntent()).isNull();
+ }
+
+ @Test
+ public void restartGame_failurePermissionDenied() throws Exception {
+ mGameServiceProviderInstance.start();
+ startTask(10, GAME_A_MAIN_ACTIVITY);
+ mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ mFakeGameService.requestCreateGameSession(10);
+ IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement(
+ mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController;
+ mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY);
+ assertThrows(SecurityException.class,
+ () -> gameSessionController.restartGame(10));
}
private void startTask(int taskId, ComponentName componentName) {
@@ -774,6 +902,21 @@
}
}
+ private void mockPermissionGranted(String permission) {
+ mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void mockPermissionDenied(String permission) {
+ mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED);
+ }
+
+ private void dispatchTaskSystemBarsEvent(
+ ThrowingConsumer<TaskSystemBarsListener> taskSystemBarsListenerConsumer) {
+ for (TaskSystemBarsListener listener : mTaskSystemBarsListeners) {
+ taskSystemBarsListenerConsumer.accept(listener);
+ }
+ }
+
static final class FakeGameService extends IGameService.Stub {
private IGameServiceController mGameServiceController;
@@ -888,6 +1031,7 @@
private static class FakeGameSession extends IGameSession.Stub {
boolean mIsDestroyed = false;
boolean mIsFocused = false;
+ boolean mAreTransientSystemBarsVisibleFromRevealGesture = false;
@Override
public void onDestroyed() {
@@ -898,15 +1042,35 @@
public void onTaskFocusChanged(boolean focused) {
mIsFocused = focused;
}
+
+ @Override
+ public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean areVisible) {
+ mAreTransientSystemBarsVisibleFromRevealGesture = areVisible;
+ }
}
- private final class FakeContext extends ContextWrapper {
+ private final class MockContext extends ContextWrapper {
private Intent mLastStartedIntent;
+ // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+ private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
- FakeContext(Context base) {
+ MockContext(Context base) {
super(base);
}
+ /**
+ * Mock checks for the specified permission, and have them behave as per {@code granted}.
+ *
+ * <p>Passing null reverts to default behavior, which does a real permission check on the
+ * test package.
+ *
+ * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+ * {@link PackageManager#PERMISSION_DENIED}.
+ */
+ public void setPermission(String permission, Integer granted) {
+ mMockedPermissions.put(permission, granted);
+ }
+
@Override
public PackageManager getPackageManager() {
return mMockPackageManager;
@@ -919,12 +1083,19 @@
@Override
public void enforceCallingPermission(String permission, @Nullable String message) {
- // Do nothing.
+ final Integer granted = mMockedPermissions.get(permission);
+ if (granted == null) {
+ super.enforceCallingOrSelfPermission(permission, message);
+ return;
+ }
+
+ if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("[Test] permission denied: " + permission);
+ }
}
Intent getLastStartedIntent() {
return mLastStartedIntent;
}
}
-
}
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/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index bdeb2b4..f9bdad6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -204,6 +204,49 @@
jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
}
+ @Test
+ public void testGetMinJobExecutionGuaranteeMs() {
+ JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(1).setExpedited(true));
+ JobStatus ejHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(2).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus ejMaxDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(3).setExpedited(true));
+ JobStatus ejHighDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(4).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jobHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+ createJobInfo(6));
+
+ spyOn(ejMax);
+ spyOn(ejHigh);
+ spyOn(ejMaxDowngraded);
+ spyOn(ejHighDowngraded);
+ spyOn(jobHigh);
+ spyOn(jobDef);
+
+ when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ejMaxDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
+ when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
+ when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
+ when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
+
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(ejMax));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(ejHigh));
+ assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(ejMaxDowngraded));
+ assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(ejHighDowngraded));
+ assertEquals(mService.mConstants.RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobHigh));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobDef));
+ }
+
/**
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
* with the correct delay and deadline constraints if the periodic job is scheduled with the
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 153ce17..9d6793c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -30,6 +30,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -536,6 +537,7 @@
ExecutionStats expectedStats = new ExecutionStats();
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
@@ -595,6 +597,7 @@
assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test"));
ExecutionStats expectedStats = new ExecutionStats();
+ expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
@@ -638,11 +641,13 @@
ExecutionStats expectedStats = new ExecutionStats();
ExecutionStats inputStats = new ExecutionStats();
+ inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
// Invalid time is now +24 hours since there are no sessions at all for the app.
expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
synchronized (mQuotaController.mLock) {
mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
}
@@ -827,6 +832,8 @@
ExecutionStats expectedStats = new ExecutionStats();
ExecutionStats inputStats = new ExecutionStats();
+ inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs =
+ 10 * MINUTE_IN_MILLIS;
inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
inputStats.sessionCountLimit = expectedStats.sessionCountLimit =
@@ -924,6 +931,7 @@
ExecutionStats expectedStats = new ExecutionStats();
// Active
+ expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
@@ -1006,6 +1014,7 @@
ExecutionStats expectedStats = new ExecutionStats();
// Active
+ expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
@@ -1242,6 +1251,7 @@
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS);
ExecutionStats expectedStats = new ExecutionStats();
+ expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs;
expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
@@ -1261,6 +1271,7 @@
assertTrue(originalStatsActive == newStatsActive);
assertEquals(expectedStats, newStatsActive);
+ expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs;
expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
@@ -1277,6 +1288,7 @@
assertTrue(originalStatsWorking == newStatsWorking);
assertNotEquals(expectedStats, newStatsWorking);
+ expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs;
expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
@@ -1293,6 +1305,7 @@
assertTrue(originalStatsFrequent == newStatsFrequent);
assertNotEquals(expectedStats, newStatsFrequent);
+ expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs;
expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
@@ -1354,7 +1367,8 @@
@Test
public void testGetMaxJobExecutionTimeLocked_Regular_Active() {
JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0);
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ 10 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS);
setDischarging();
@@ -2886,11 +2900,12 @@
public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
// Set rate limiting period different from allowed time to confirm code sets based on
// the former.
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS);
+ final int standbyBucket = WORKING_INDEX;
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ 10 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS);
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- final int standbyBucket = WORKING_INDEX;
JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
setStandbyBucket(standbyBucket, jobStatus);
@@ -2953,8 +2968,8 @@
@Test
public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
// Make sure any new value is used correctly.
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -2977,8 +2992,8 @@
// Make sure any new value is used correctly.
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
mQcConstants.IN_QUOTA_BUFFER_MS * 2);
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS,
- mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
mQcConstants.MAX_EXECUTION_TIME_MS / 2);
@@ -3002,7 +3017,8 @@
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
- final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
+ final long remainingTimeMs =
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs;
// Session straddles edge of bucket window. Only the contribution should be counted towards
// the quota.
@@ -3062,16 +3078,28 @@
@Test
public void testConstantsUpdating_ValidValues() {
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 8 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ 5 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ 7 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ 11 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000);
@@ -3079,6 +3107,7 @@
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000);
setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300);
@@ -3088,6 +3117,7 @@
setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
10 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS);
@@ -3104,10 +3134,23 @@
84 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
- assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+ assertEquals(8 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+ assertEquals(5 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+ assertEquals(2 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+ assertEquals(4 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+ assertEquals(11 * MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+ assertEquals(99 * MINUTE_IN_MILLIS,
+ mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(45 * MINUTE_IN_MILLIS,
@@ -3118,12 +3161,14 @@
assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+ assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+ assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -3132,6 +3177,7 @@
assertEquals(10 * SECOND_IN_MILLIS,
mQuotaController.getTimingSessionCoalescingDurationMs());
assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
+ assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
@@ -3151,16 +3197,24 @@
@Test
public void testConstantsUpdating_InvalidValues() {
// Test negatives/too low.
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS);
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1);
@@ -3168,6 +3222,7 @@
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1);
setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS);
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0);
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0);
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3);
@@ -3176,6 +3231,7 @@
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0);
setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1);
setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1);
@@ -3191,10 +3247,19 @@
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
- assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
assertEquals(0, mQuotaController.getInQuotaBufferMs());
assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+ assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3203,12 +3268,14 @@
assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+ assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+ assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -3216,6 +3283,7 @@
assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs());
+ assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
@@ -3233,17 +3301,37 @@
// Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ 2 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ 10 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS);
assertTrue(mQuotaController.getInQuotaBufferMs()
- <= mQuotaController.getAllowedTimePerPeriodMs());
+ <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
// Test larger than a day. Controller should cap at one day.
- setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
+ 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f);
setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -3254,6 +3342,7 @@
setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -3269,10 +3358,21 @@
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
- assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
+ assertEquals(24 * HOUR_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS,
+ mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
+ assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3284,6 +3384,7 @@
assertEquals(15 * MINUTE_IN_MILLIS,
mQuotaController.getTimingSessionCoalescingDurationMs());
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
+ assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 1e0f30e..4ec1641 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -69,6 +69,7 @@
import com.android.server.pm.pkg.parsing.ParsingPackage
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
@@ -169,24 +170,6 @@
mSettingsMap.putAll(mPreExistingSettings)
!mPreExistingSettings.isEmpty()
}
- whenever(mocks.settings.setPackageStoppedStateLPw(any(), any(), anyBoolean(), anyInt())) {
- val pm: PackageManagerService = getArgument(0)
- val pkgSetting = mSettingsMap[getArgument(1)]!!
- val stopped: Boolean = getArgument(2)
- val userId: Int = getArgument(3)
- return@whenever if (pkgSetting.getStopped(userId) != stopped) {
- pkgSetting.setStopped(stopped, userId)
- if (pkgSetting.getNotLaunched(userId)) {
- pkgSetting.installSource.installerPackageName?.let {
- pm.notifyFirstLaunch(pkgSetting.packageName, it, userId)
- }
- pkgSetting.setNotLaunched(false, userId)
- }
- true
- } else {
- false
- }
- }
}
/** Collection of mocks used for PackageManagerService tests. */
@@ -210,7 +193,9 @@
val packageParser: PackageParser2 = mock()
val keySetManagerService: KeySetManagerService = mock()
val packageAbiHelper: PackageAbiHelper = mock()
- val appsFilter: AppsFilter = mock()
+ val appsFilter: AppsFilter = mock {
+ whenever(snapshot()) { this@mock }
+ }
val dexManager: DexManager = mock()
val installer: Installer = mock()
val displayMetrics: DisplayMetrics = mock()
@@ -351,6 +336,7 @@
stageServicesExtensionScan()
stageSystemSharedLibraryScan()
stagePermissionsControllerScan()
+ stageSupplementalProcessScan()
stageInstantAppResolverScan()
}
@@ -585,6 +571,22 @@
}
@Throws(Exception::class)
+ private fun stageSupplementalProcessScan() {
+ stageScanNewPackage("com.android.supplemental.process",
+ 1L, systemPartitions[0].privAppFolder,
+ withPackage = { pkg: PackageImpl ->
+ val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
+ mockQueryServices(SupplementalProcessManagerLocal.SERVICE_INTERFACE,
+ createBasicServiceInfo(
+ pkg, applicationInfo, "SupplementalProcessService"))
+ pkg
+ },
+ withSetting = { setting: PackageSettingBuilder ->
+ setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+ })
+ }
+
+ @Throws(Exception::class)
private fun stageSystemSharedLibraryScan() {
stageScanNewPackage("android.ext.shared",
1L, systemPartitions[0].appFolder,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
index edbfecc..ccfeb4c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -59,8 +59,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
rule.system().validateFinalState()
return pms
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index 0820a3c..bbca121 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -63,8 +63,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index cfc81e6..a6c7bfb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -170,7 +170,6 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 2735e3d..b89f36f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -24,6 +24,7 @@
import android.os.storage.StorageManager
import android.util.ArrayMap
import android.util.PackageUtils
+import com.android.internal.util.FunctionalUtils
import com.android.server.SystemConfig.SharedLibraryEntry
import com.android.server.compat.PlatformCompat
import com.android.server.extendedtestutils.wheneverStatic
@@ -34,6 +35,7 @@
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.mock
+import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.nullable
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
@@ -41,11 +43,14 @@
import com.google.common.truth.Truth.assertThat
import libcore.util.HexEncoding
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -96,10 +101,14 @@
mRule.system().stageNominalSystemState()
addExistingPackages()
- val testParams = PackageManagerServiceTestParams().apply {
- packages = mExistingPackages
- }
- mPms = spy(PackageManagerService(mRule.mocks().injector, testParams))
+ mPms = spy(PackageManagerService(mRule.mocks().injector,
+ false /*coreOnly*/,
+ false /*factoryTest*/,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /*isEngBuild*/,
+ false /*isUserDebugBuild*/,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL))
mSettings = mRule.mocks().injector.settings
mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector)
mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper)
@@ -109,7 +118,19 @@
whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java))
.thenReturn(mStorageManager)
whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile)
- doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any())
+ doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageName(any(), any())
+ doAnswer {
+ it.getArgument<FunctionalUtils.ThrowingConsumer<Computer>>(0).acceptOrThrow(
+ mockThrowOnUnmocked {
+ whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries }
+ whenever(resolveInternalPackageName(anyString(), anyLong())) {
+ mPms.resolveInternalPackageName(getArgument(0), getArgument(1))
+ }
+ whenever(getPackageStateInternal(anyString())) {
+ mPms.getPackageStateInternal(getArgument(0))
+ }
+ })
+ }.`when`(mPms).executeWithConsistentComputer(any())
whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any()))
.thenReturn(PackageManager.DELETE_SUCCEEDED)
whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat)
@@ -232,6 +253,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateSharedLibraries_withStaticLibPackage() {
val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
@@ -244,6 +266,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateSharedLibraries_withConsumerPackage() {
val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
@@ -257,6 +280,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateAllSharedLibraries() {
mExistingSettings.forEach {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index bdfdf77..64657a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -50,7 +50,7 @@
import android.util.SparseArray;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
@@ -101,12 +101,12 @@
mMockitoSession = ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties.class)
- .mockStatic(PackageHelper.class)
+ .mockStatic(InstallLocationUtils.class)
.startMocking();
when(mStorageManager.supportsCheckpoint()).thenReturn(true);
when(mStorageManager.needsCheckpoint()).thenReturn(true);
- when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+ when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager);
when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index fe7e2d9..ac406b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -128,7 +128,7 @@
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
- verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -214,7 +214,7 @@
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
- verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -295,7 +295,7 @@
{ suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID)
testHandler.flush()
- verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -499,8 +499,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
rule.system().validateFinalState()
return pms
}
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/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
new file mode 100644
index 0000000..c325778
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.R;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DockObserverTest {
+
+ @Rule
+ public TestableContext mContext =
+ new TestableContext(ApplicationProvider.getApplicationContext(), null);
+
+ private final BroadcastInterceptingContext mInterceptingContext =
+ new BroadcastInterceptingContext(mContext);
+
+ BroadcastInterceptingContext.FutureIntent updateExtconDockState(DockObserver observer,
+ String extconDockState) {
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ mInterceptingContext.nextBroadcastIntent(Intent.ACTION_DOCK_EVENT);
+ observer.setDockStateFromProviderForTesting(
+ DockObserver.ExtconStateProvider.fromString(extconDockState));
+ TestableLooper.get(this).processAllMessages();
+ return futureIntent;
+ }
+
+ DockObserver observerWithMappingConfig(String[] configEntries) {
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_dockExtconStateMapping,
+ configEntries);
+ return new DockObserver(mInterceptingContext);
+ }
+
+ void assertDockEventIntentWithExtraThenUndock(DockObserver observer, String extconDockState,
+ int expectedExtra) throws ExecutionException, InterruptedException {
+ assertThat(updateExtconDockState(observer, extconDockState)
+ .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(expectedExtra);
+ assertThat(updateExtconDockState(observer, "DOCK=0")
+ .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ }
+
+ @Before
+ public void setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Test
+ public void testDockIntentBroadcast_onlyAfterBootReady()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ updateExtconDockState(observer, "DOCK=1").assertNotReceived();
+ // Last boot phase reached
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
+
+ @Test
+ public void testDockIntentBroadcast_customConfigResource()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = observerWithMappingConfig(
+ new String[] {"2,KEY1=1,KEY2=2", "3,KEY3=3"});
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ // Mapping should not match
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1",
+ Intent.EXTRA_DOCK_STATE_DESK);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY1=1",
+ Intent.EXTRA_DOCK_STATE_DESK);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2",
+ Intent.EXTRA_DOCK_STATE_DESK);
+
+ // 1st mapping now matches
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2\nKEY1=1",
+ Intent.EXTRA_DOCK_STATE_CAR);
+
+ // 2nd mapping now matches
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY3=3",
+ Intent.EXTRA_DOCK_STATE_LE_DESK);
+ }
+
+ @Test
+ public void testDockIntentBroadcast_customConfigResourceWithWildcard()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = observerWithMappingConfig(new String[] {
+ "2,KEY2=2",
+ "3,KEY3=3",
+ "4"
+ });
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
+ Intent.EXTRA_DOCK_STATE_HE_DESK);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index e40f543..e1aa08d 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
@@ -40,6 +41,7 @@
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -68,6 +70,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -126,7 +130,7 @@
mUsageEventListener = mUsageEventListenerCaptor.getValue();
doReturn(mUserInfos).when(mUserManager).getUsers();
-
+ doReturn(true).when(mPackageManagerInternal).canQueryPackage(anyInt(), any());
doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
anyInt(), anyBoolean(), anyBoolean(), any(), any());
@@ -376,6 +380,58 @@
assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
}
+ @Test
+ public void testGetHibernationStatsForUser_getsStatsForPackage() {
+ // GIVEN a package is hibernating globally and for a user
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
+
+ // WHEN we ask for the hibernation stats for the package
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+ // THEN the stats exist for the package
+ assertTrue(stats.containsKey(PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testGetHibernationStatsForUser_noExceptionThrownWhenPackageDoesntExist() {
+ // WHEN we ask for the hibernation stats for a package that doesn't exist
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+ // THEN no exception is thrown and empty stats are returned
+ assertNotNull(stats);
+ }
+
+ @Test
+ public void testGetHibernationStatsForUser_returnsAllIfNoPackagesSpecified()
+ throws RemoteException {
+ // GIVEN an unlocked user with all packages installed and they're all hibernating
+ UserInfo userInfo =
+ addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_2, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_3, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_3, USER_ID_2, true);
+
+ // WHEN we ask for the hibernation stats with no package specified
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ null /* packageNames */, USER_ID_2);
+
+ // THEN all the package stats are returned
+ assertTrue(stats.containsKey(PACKAGE_NAME_1));
+ assertTrue(stats.containsKey(PACKAGE_NAME_2));
+ assertTrue(stats.containsKey(PACKAGE_NAME_3));
+ }
+
/**
* Mock a usage event occurring.
*
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 9ee1205..3890d4d 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -24,14 +24,20 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
@@ -42,6 +48,7 @@
import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
+import android.service.attention.IProximityCallback;
import androidx.test.filters.SmallTest;
@@ -49,6 +56,7 @@
import com.android.server.attention.AttentionManagerService.AttentionCheckCache;
import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer;
import com.android.server.attention.AttentionManagerService.AttentionHandler;
+import com.android.server.attention.AttentionManagerService.ProximityUpdate;
import org.junit.Before;
import org.junit.Test;
@@ -59,10 +67,13 @@
/**
* Tests for {@link com.android.server.attention.AttentionManagerService}
*/
+@SuppressWarnings("GuardedBy")
@SmallTest
public class AttentionManagerServiceTest {
+ private static final double PROXIMITY_SUCCESS_STATE = 1.0;
private AttentionManagerService mSpyAttentionManager;
private final int mTimeout = 1000;
+ private final Object mLock = new Object();
@Mock
private AttentionCallbackInternal mMockAttentionCallbackInternal;
@Mock
@@ -73,6 +84,8 @@
private IThermalService mMockIThermalService;
@Mock
Context mContext;
+ @Mock
+ private ProximityCallbackInternal mMockProximityCallbackInternal;
@Before
public void setUp() throws RemoteException {
@@ -84,7 +97,6 @@
doReturn(true).when(mMockIPowerManager).isInteractive();
mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null);
- Object mLock = new Object();
// setup a spy on attention manager
AttentionManagerService attentionManager = new AttentionManagerService(
mContext,
@@ -100,6 +112,119 @@
mSpyAttentionManager);
mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck;
mSpyAttentionManager.mService = new MockIAttentionService();
+ doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() {
+ mSpyAttentionManager.mIsServiceEnabled = false;
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(false).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isFalse();
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ @Test
+ public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+
+ ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate;
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ .isTrue();
+ assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ verifyZeroInteractions(mMockProximityCallbackInternal);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+ ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() {
+ @Override
+ public void onProximityUpdate(double distance) {
+ fail("Callback shouldn't have responded.");
+ }
+ };
+ mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback);
+
+ verifyNoMoreInteractions(mMockProximityCallbackInternal);
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched()
+ throws RemoteException {
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+
+ assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull();
+ }
+
+ @Test
+ public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException {
+ // Attention Service registers proximity updates.
+ mSpyAttentionManager.mIsServiceEnabled = true;
+ doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
+ doReturn(true).when(mMockIPowerManager).isInteractive();
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
+ verify(mMockProximityCallbackInternal, times(1))
+ .onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+
+ // Attention Service unregisters the proximity update twice in a row.
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityCallbackInternal);
}
@Test
@@ -127,7 +252,6 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
mSpyAttentionManager.mCurrentAttentionCheck = null;
AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
@@ -213,6 +337,13 @@
public void cancelAttentionCheck(IAttentionCallback callback) {
}
+ public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException {
+ callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
+ }
+
+ public void onStopProximityUpdates() throws RemoteException {
+ }
+
public IBinder asBinder() {
return null;
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 9e1445c..dad9fe8 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -28,9 +28,10 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
-import android.media.BtProfileConnectionInfo;
+import android.media.BluetoothProfileConnectionInfo;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -100,7 +101,7 @@
mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
- BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource"));
+ BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource"));
Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
verify(mSpyDevInventory, times(1)).setBluetoothActiveDevice(
any(AudioDeviceBroker.BtDeviceInfo.class)
@@ -186,8 +187,9 @@
doNothing().when(mSpySystemServer).broadcastStickyIntentToCurrentProfileGroup(
any(Intent.class));
- mSpyDevInventory.setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
- AudioService.CONNECTION_STATE_CONNECTED, address, name, caller);
+ mSpyDevInventory.setWiredDeviceConnectionState(new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET, address, name),
+ AudioService.CONNECTION_STATE_CONNECTED, caller);
Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
// Verify that the sticky intent is broadcasted
@@ -208,13 +210,13 @@
// first connection: ensure the device is connected as a starting condition for the test
mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
- BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource"));
+ BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource"));
Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
// disconnection
mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(null, mFakeBtDevice,
- BtProfileConnectionInfo.a2dpInfo(false, -1), "testSource"));
+ BluetoothProfileConnectionInfo.createA2dpInfo(false, -1), "testSource"));
if (delayAfterDisconnection > 0) {
Thread.sleep(delayAfterDisconnection);
}
@@ -222,7 +224,7 @@
// reconnection
mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
- BtProfileConnectionInfo.a2dpInfo(true, 2), "testSource"));
+ BluetoothProfileConnectionInfo.createA2dpInfo(true, 2), "testSource"));
Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
// Verify disconnection has been cancelled and we're seeing two connections attempts,
@@ -246,11 +248,11 @@
*/
private void checkSingleSystemConnection(BluetoothDevice btDevice) throws Exception {
final String expectedName = btDevice.getName() == null ? "" : btDevice.getName();
+ AudioDeviceAttributes expected = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, btDevice.getAddress(), expectedName);
verify(mSpyAudioSystem, times(1)).setDeviceConnectionState(
- ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP),
+ ArgumentMatchers.argThat(x -> x.equalTypeAddress(expected)),
ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE),
- ArgumentMatchers.eq(btDevice.getAddress()),
- ArgumentMatchers.eq(expectedName),
anyInt() /*codec*/);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 8d706cb..1f355b0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -48,11 +48,10 @@
//-----------------------------------------------------------------
// Overrides of AudioSystemAdapter
@Override
- public int setDeviceConnectionState(int device, int state, String deviceAddress,
- String deviceName, int codecFormat) {
- Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, %s, %s, 0x%s",
- Integer.toHexString(device), state, deviceAddress, deviceName,
- Integer.toHexString(codecFormat)));
+ public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
+ int codecFormat) {
+ Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %d, 0x%s",
+ attributes.toString(), state, Integer.toHexString(codecFormat)));
return AudioSystem.AUDIO_STATUS_OK;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
new file mode 100644
index 0000000..5746f6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatusBarManager;
+import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.OperationReason;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.statusbar.ISessionListener;
+import com.android.internal.statusbar.IStatusBarService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class BiometricContextProviderTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IStatusBarService mStatusBarService;
+ @Mock
+ private ISessionListener mSessionListener;
+ @Mock
+ private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+
+ private OperationContext mOpContext = new OperationContext();
+ private IBiometricContextListener mListener;
+ private BiometricContextProvider mProvider;
+
+ @Before
+ public void setup() throws RemoteException {
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService,
+ null /* handler */);
+ ArgumentCaptor<IBiometricContextListener> captor =
+ ArgumentCaptor.forClass(IBiometricContextListener.class);
+ verify(mStatusBarService).setBiometicContextListener(captor.capture());
+ mListener = captor.getValue();
+ ArgumentCaptor<ISessionListener> sessionCaptor =
+ ArgumentCaptor.forClass(ISessionListener.class);
+ verify(mStatusBarService).registerSessionListener(anyInt(), sessionCaptor.capture());
+ mSessionListener = sessionCaptor.getValue();
+ }
+
+ @Test
+ public void testIsAoD() throws RemoteException {
+ mListener.onDozeChanged(true);
+ assertThat(mProvider.isAoD()).isTrue();
+ mListener.onDozeChanged(false);
+ assertThat(mProvider.isAoD()).isFalse();
+
+ when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
+ mListener.onDozeChanged(true);
+ assertThat(mProvider.isAoD()).isFalse();
+ mListener.onDozeChanged(false);
+ assertThat(mProvider.isAoD()).isFalse();
+ }
+
+ @Test
+ public void testSubscribesToAoD() throws RemoteException {
+ final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
+ final List<Boolean> actual = new ArrayList<>();
+
+ mProvider.subscribe(mOpContext, ctx -> {
+ assertThat(ctx).isSameInstanceAs(mOpContext);
+ actual.add(ctx.isAoD);
+ });
+
+ for (boolean v : expected) {
+ mListener.onDozeChanged(v);
+ }
+
+ assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+ }
+
+ @Test
+ public void testUnsubscribes() throws RemoteException {
+ final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
+ mProvider.subscribe(mOpContext, emptyConsumer);
+ mProvider.unsubscribe(mOpContext);
+
+ mListener.onDozeChanged(true);
+
+ final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
+ mProvider.subscribe(mOpContext, nonEmptyConsumer);
+ mListener.onDozeChanged(false);
+ mProvider.unsubscribe(mOpContext);
+ mListener.onDozeChanged(true);
+
+ verify(emptyConsumer, never()).accept(any());
+ verify(nonEmptyConsumer).accept(same(mOpContext));
+ }
+
+ @Test
+ public void testSessionId() throws RemoteException {
+ final int keyguardSessionId = 10;
+ final int bpSessionId = 20;
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+ mSessionListener.onSessionStarted(StatusBarManager.SESSION_KEYGUARD,
+ InstanceId.fakeInstanceId(keyguardSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+ mSessionListener.onSessionStarted(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ InstanceId.fakeInstanceId(bpSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+ assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId);
+
+ mSessionListener.onSessionEnded(StatusBarManager.SESSION_KEYGUARD,
+ InstanceId.fakeInstanceId(keyguardSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId);
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+
+ mSessionListener.onSessionEnded(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ InstanceId.fakeInstanceId(bpSessionId));
+
+ assertThat(mProvider.getBiometricPromptSessionId()).isNull();
+ assertThat(mProvider.getKeyguardEntrySessionId()).isNull();
+ }
+
+ @Test
+ public void testUpdate() throws RemoteException {
+ mListener.onDozeChanged(false);
+ OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+ // default state when nothing has been set
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(0);
+ assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+ assertThat(mOpContext.isAoD).isEqualTo(false);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+ for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT,
+ StatusBarManager.SESSION_KEYGUARD)) {
+ final int id = 40 + type;
+ final boolean aod = (type & 1) == 0;
+
+ mListener.onDozeChanged(aod);
+ mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
+ context = mProvider.updateContext(mOpContext, false /* crypto */);
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(id);
+ assertThat(mOpContext.reason).isEqualTo(reason(type));
+ assertThat(mOpContext.isAoD).isEqualTo(aod);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+
+ mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id));
+ }
+
+ context = mProvider.updateContext(mOpContext, false /* crypto */);
+ assertThat(context).isSameInstanceAs(mOpContext);
+ assertThat(mOpContext.id).isEqualTo(0);
+ assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN);
+ assertThat(mOpContext.isAoD).isEqualTo(false);
+ assertThat(mOpContext.isCrypto).isEqualTo(false);
+ }
+
+ private static byte reason(int type) {
+ if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
+ return OperationReason.BIOMETRIC_PROMPT;
+ }
+ if (type == StatusBarManager.SESSION_KEYGUARD) {
+ return OperationReason.KEYGUARD;
+ }
+ return OperationReason.UNKNOWN;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 2b72fab..fe02337 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -31,6 +31,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.input.InputSensorInfo;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -44,7 +45,8 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@Presubmit
@SmallTest
@@ -55,6 +57,9 @@
private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT;
@Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Rule
public TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
@Mock
@@ -64,12 +69,12 @@
@Mock
private BaseClientMonitor mClient;
+ private OperationContext mOpContext;
private BiometricLogger mLogger;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
+ mOpContext = new OperationContext();
mContext.addMockSystemService(SensorManager.class, mSensorManager);
when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(
new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0,
@@ -91,14 +96,13 @@
final int acquiredInfo = 2;
final int vendorCode = 3;
- final boolean isCrypto = true;
final int targetUserId = 9;
- mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId);
+ mLogger.logOnAcquired(mContext, mOpContext, acquiredInfo, vendorCode, targetUserId);
- verify(mSink).acquired(
+ verify(mSink).acquired(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+ eq(acquiredInfo), eq(vendorCode), eq(targetUserId));
}
@Test
@@ -107,17 +111,16 @@
final boolean authenticated = true;
final boolean requireConfirmation = false;
- final boolean isCrypto = false;
final int targetUserId = 11;
final boolean isBiometricPrompt = true;
- mLogger.logOnAuthenticated(mContext,
- authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt);
+ mLogger.logOnAuthenticated(mContext, mOpContext,
+ authenticated, requireConfirmation, targetUserId, isBiometricPrompt);
- verify(mSink).authenticate(
+ verify(mSink).authenticate(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto),
- eq(targetUserId), eq(isBiometricPrompt), anyFloat());
+ anyLong(), anyInt(), eq(requireConfirmation),
+ eq(targetUserId), anyFloat());
}
@Test
@@ -141,14 +144,13 @@
final int error = 7;
final int vendorCode = 11;
- final boolean isCrypto = false;
final int targetUserId = 9;
- mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId);
+ mLogger.logOnError(mContext, mOpContext, error, vendorCode, targetUserId);
- verify(mSink).error(
+ verify(mSink).error(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
- anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId));
+ anyLong(), eq(error), eq(vendorCode), eq(targetUserId));
}
@Test
@@ -173,38 +175,34 @@
private void testDisabledMetrics(boolean isBadConfig) {
mLogger.disableMetrics();
- mLogger.logOnAcquired(mContext,
+ mLogger.logOnAcquired(mContext, mOpContext,
0 /* acquiredInfo */,
1 /* vendorCode */,
- true /* isCrypto */,
8 /* targetUserId */);
- mLogger.logOnAuthenticated(mContext,
+ mLogger.logOnAuthenticated(mContext, mOpContext,
true /* authenticated */,
true /* requireConfirmation */,
- false /* isCrypto */,
4 /* targetUserId */,
true/* isBiometricPrompt */);
mLogger.logOnEnrolled(2 /* targetUserId */,
10 /* latency */,
true /* enrollSuccessful */);
- mLogger.logOnError(mContext,
+ mLogger.logOnError(mContext, mOpContext,
4 /* error */,
0 /* vendorCode */,
- false /* isCrypto */,
6 /* targetUserId */);
- verify(mSink, never()).acquired(
+ verify(mSink, never()).acquired(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyInt(), anyInt(), anyBoolean(), anyInt());
- verify(mSink, never()).authenticate(
+ anyInt(), anyInt(), anyInt());
+ verify(mSink, never()).authenticate(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyLong(), anyBoolean(), anyInt(), anyBoolean(),
- anyBoolean(), anyInt(), anyBoolean(), anyFloat());
+ anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
verify(mSink, never()).enroll(
anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat());
- verify(mSink, never()).error(
+ verify(mSink, never()).error(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
- anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt());
+ anyLong(), anyInt(), anyInt(), anyInt());
mLogger.logUnknownEnrollmentInFramework();
mLogger.logUnknownEnrollmentInHal();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index fc55a9f..2d9d868 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -22,6 +22,7 @@
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -34,11 +35,16 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Supplier;
+
@Presubmit
@SmallTest
public class AcquisitionClientTest {
@@ -87,12 +93,11 @@
boolean mHalOperationRunning;
public TestAcquisitionClient(@NonNull Context context,
- @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter callback) {
super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */,
- TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */,
- 0 /* statsAction */,
- 0 /* statsClient */);
+ TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 51d234d..8e6d90c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -30,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import org.junit.Before;
@@ -49,6 +50,8 @@
@Mock
private BiometricLogger mLogger;
@Mock
+ private BiometricContext mBiometricContext;
+ @Mock
private ClientMonitorCallback mCallback;
private TestClientMonitor mClientMonitor;
@@ -109,7 +112,7 @@
TestClientMonitor() {
super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */,
- 5 /* sensorId */, mLogger);
+ 5 /* sensorId */, mLogger, mBiometricContext);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 8751cf3..64be569 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -36,6 +36,9 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +57,8 @@
public abstract static class InterruptableMonitor<T>
extends HalClientMonitor<T> implements Interruptable {
public InterruptableMonitor() {
- super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
+ super(null, null, null, null, 0, null, 0, 0,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index c99d656..0fa2b41 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -51,6 +51,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.nano.BiometricSchedulerProto;
import com.android.server.biometrics.nano.BiometricsProto;
@@ -61,6 +63,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Supplier;
+
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -93,7 +97,7 @@
@Test
public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
- final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+ final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
final HalClientMonitor<Object> client1 =
new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
@@ -184,7 +188,7 @@
@Test
public void testCancelNotInvoked_whenOperationWaitingForCookie() {
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class);
+ final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
@@ -296,7 +300,7 @@
@Test
public void testCancelPendingAuth() throws RemoteException {
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
@@ -360,7 +364,7 @@
private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
testCancelsWhenRequestId(requestId, cancelRequestId, started,
new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
@@ -383,7 +387,7 @@
private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
testCancelsWhenRequestId(requestId, cancelRequestId, started,
new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
@@ -441,7 +445,7 @@
public void testCancelsPending_whenAuthRequestIdsSet() {
final long requestId1 = 10;
final long requestId2 = 20;
- final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final Supplier<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(
mContext, lazyDaemon, mToken, callback);
@@ -500,7 +504,7 @@
@Test
public void testClientDestroyed_afterFinish() {
- final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+ final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
final TestHalClientMonitor client =
new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
mScheduler.scheduleClientMonitor(client);
@@ -520,14 +524,14 @@
int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener) {
super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
- TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
- 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
- false /* isKeyguard */, true /* shouldVibrate */,
- false /* isKeyguardBypassEnabled */);
+ TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+ true /* isStrongBiometric */, null /* taskStackListener */,
+ mock(LockoutTracker.class), false /* isKeyguard */,
+ true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
}
@Override
@@ -567,12 +571,13 @@
int mNumCancels = 0;
TestEnrollClient(@NonNull Context context,
- @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+ @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener) {
super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
"test" /* owner */, mock(BiometricUtils.class),
- 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
- true /* shouldVibrate */);
+ 5 /* timeoutSec */, TEST_SENSOR_ID,
+ true /* shouldVibrate */,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
}
@Override
@@ -604,15 +609,15 @@
private boolean mDestroyed;
TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
- @NonNull LazyDaemon<Object> lazyDaemon) {
+ @NonNull Supplier<Object> lazyDaemon) {
this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
}
TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
- @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
+ @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
- TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
- 0 /* statsAction */, 0 /* statsClient */);
+ TAG, cookie, TEST_SENSOR_ID,
+ mock(BiometricLogger.class), mock(BiometricContext.class));
mProtoEnum = protoEnum;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
index 587bb60..092ca19 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -64,7 +64,7 @@
ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks);
callback.onClientStarted(mClientMonitor);
- final InOrder order = inOrder(expected);
+ final InOrder order = inOrder((Object[]) expected);
for (ClientMonitorCallback cb : expected) {
order.verify(cb).onClientStarted(eq(mClientMonitor));
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index a11709a..52eee9a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -41,12 +41,17 @@
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Supplier;
+
@Presubmit
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -64,6 +69,10 @@
private Context mContext;
@Mock
private IBiometricService mBiometricService;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
@@ -86,7 +95,8 @@
@Override
public StopUserClient<?> getStopUserClient(int userId) {
return new TestStopUserClient(mContext, Object::new, mToken, userId,
- TEST_SENSOR_ID, mUserStoppedCallback);
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStoppedCallback);
}
@NonNull
@@ -94,7 +104,8 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
mStartUserClientCount++;
return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
- TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish);
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStartedCallback, mStartOperationsFinish);
}
},
CoexCoordinator.getInstance());
@@ -215,7 +226,7 @@
int numInvocations;
@Override
- public void onUserStarted(int newUserId, Object newObject) {
+ public void onUserStarted(int newUserId, Object newObject, int halInterfaceVersion) {
numInvocations++;
mCurrentUserId = newUserId;
}
@@ -223,9 +234,11 @@
private static class TestStopUserClient extends StopUserClient<Object> {
public TestStopUserClient(@NonNull Context context,
- @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStoppedCallback callback) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
}
@Override
@@ -251,9 +264,11 @@
ClientMonitorCallback mCallback;
public TestStartUserClient(@NonNull Context context,
- @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
- super(context, lazyDaemon, token, userId, sensorId, callback);
+ @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext,
+ @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
+ super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
mShouldFinish = shouldFinish;
}
@@ -268,7 +283,8 @@
mCallback = callback;
if (mShouldFinish) {
- mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
+ mUserStartedCallback.onUserStarted(
+ getTargetUserId(), new Object(), 1 /* halInterfaceVersion */);
callback.onClientFinished(this, true /* success */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
new file mode 100644
index 0000000..aba93b0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.face.UsageStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceAuthenticationClientTest {
+
+ private static final int USER_ID = 12;
+ private static final long OP_ID = 32;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutCache mLockoutCache;
+ @Mock
+ private UsageStats mUsageStats;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void authNoContext_v1() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(eq(OP_ID));
+ verify(mHal, never()).authenticateWithContext(anyLong(), any());
+ }
+
+ @Test
+ public void authWithContext_v2() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).authenticateWithContext(
+ eq(OP_ID), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).authenticate(anyLong());
+ }
+
+ private FaceAuthenticationClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
+ 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+ false /* restricted */, "test-owner", 4 /* cookie */,
+ false /* requireConfirmation */, 9 /* sensorId */,
+ mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
+ mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+ false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
new file mode 100644
index 0000000..25135c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceDetectClientTest {
+
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void detectNoContext_v1() throws RemoteException {
+ final FaceDetectClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).detectInteraction();
+ verify(mHal, never()).detectInteractionWithContext(any());
+ }
+
+ @Test
+ public void detectWithContext_v2() throws RemoteException {
+ final FaceDetectClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).detectInteraction();
+ }
+
+ private FaceDetectClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceDetectClient(mContext, () -> aidl, mToken,
+ 99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
+ "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+ false /* isStrongBiometric */, null /* sensorPrivacyManager */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
new file mode 100644
index 0000000..38e048b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceEnrollClientTest {
+
+ private static final byte[] HAT = new byte[69];
+ private static final int USER_ID = 12;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mUtils;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void enrollNoContext_v1() throws RemoteException {
+ final FaceEnrollClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).enroll(any(), anyByte(), any(), any());
+ verify(mHal, never()).enrollWithContext(any(), anyByte(), any(), any(), any());
+ }
+
+ @Test
+ public void enrollWithContext_v2() throws RemoteException {
+ final FaceEnrollClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(),
+ same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).enroll(any(), anyByte(), any(), any());
+ }
+
+ private FaceEnrollClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
+ USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
+ mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
+ null /* previewSurface */, 8 /* sensorId */,
+ mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
+ true /* debugConsent */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 0ac00aa..12b8264 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -37,6 +37,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -60,6 +61,8 @@
private UserManager mUserManager;
@Mock
private IFace mDaemon;
+ @Mock
+ private BiometricContext mBiometricContext;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -89,7 +92,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
- mLockoutResetDispatcher);
+ mLockoutResetDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -139,8 +142,9 @@
@NonNull Context context,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher);
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 2718bf9..b60324e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -101,8 +107,9 @@
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
- () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
- mLockoutResetDispatcher));
+ () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext,
+ HAT, mLockoutCache, mLockoutResetDispatcher));
mLooper.dispatchAll();
verifyNotLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 21a7a8a..116d2d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -41,6 +41,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -70,6 +71,8 @@
private UserManager mUserManager;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private BiometricContext mBiometricContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -100,7 +103,8 @@
resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
- mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
+ mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
+ mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
index 931fad1..ec08329 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -34,6 +34,8 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -62,6 +64,10 @@
private IFaceServiceReceiver mOtherReceiver;
@Mock
private ClientMonitorCallback mMonitorCallback;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private FaceGenerateChallengeClient mClient;
@@ -75,7 +81,7 @@
mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
- TAG, SENSOR_ID, START_TIME);
+ TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
new file mode 100644
index 0000000..de0f038
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutCache;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintAuthenticationClientTest {
+
+ private static final int USER_ID = 8;
+ private static final long OP_ID = 7;
+ private static final int POINTER_ID = 0;
+ private static final int TOUCH_X = 8;
+ private static final int TOUCH_Y = 20;
+ private static final float TOUCH_MAJOR = 4.4f;
+ private static final float TOUCH_MINOR = 5.5f;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutCache mLockoutCache;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ISidefpsController mSideFpsController;
+ @Mock
+ private FingerprintSensorPropertiesInternal mSensorProps;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private Probe mLuxProbe;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+ new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void authNoContext_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(eq(OP_ID));
+ verify(mHal, never()).authenticateWithContext(anyLong(), any());
+ }
+
+ @Test
+ public void authWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).authenticateWithContext(
+ eq(OP_ID), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).authenticate(anyLong());
+ }
+
+ @Test
+ public void pointerUp_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUp(eq(POINTER_ID));
+ verify(mHal, never()).onPointerUpWithContext(any());
+ }
+
+ @Test
+ public void pointerDown_v1() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDown(eq(0),
+ eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ verify(mHal, never()).onPointerDownWithContext(any());
+ }
+
+ @Test
+ public void pointerUpWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void pointerDownWithContext_v2() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void luxProbeWhenFingerDown() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe).enable();
+
+ client.onAcquired(2, 0);
+ verify(mLuxProbe, never()).disable();
+
+ client.onPointerUp();
+ verify(mLuxProbe).disable();
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe, times(2)).enable();
+ }
+
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+
+ verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
+ OperationContext opContext = mOperationContextCaptor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(eq(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(opContext));
+ }
+
+ @Test
+ public void showHideOverlay_cancel() throws RemoteException {
+ showHideOverlay(c -> c.cancel());
+ }
+
+ @Test
+ public void showHideOverlay_stop() throws RemoteException {
+ showHideOverlay(c -> c.stopHalOperation());
+ }
+
+ @Test
+ public void showHideOverlay_error() throws RemoteException {
+ showHideOverlay(c -> c.onError(0, 0));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void showHideOverlay_lockout() throws RemoteException {
+ showHideOverlay(c -> c.onLockoutTimed(5000));
+ }
+
+ @Test
+ public void showHideOverlay_lockoutPerm() throws RemoteException {
+ showHideOverlay(c -> c.onLockoutPermanent());
+ }
+
+ private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
+ throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+
+ client.start(mCallback);
+
+ verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+ verify(mSideFpsController).show(anyInt(), anyInt());
+
+ block.accept(client);
+
+ verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+ verify(mSideFpsController).hide(anyInt());
+ }
+
+ private FingerprintAuthenticationClient createClient() throws RemoteException {
+ return createClient(100);
+ }
+
+ private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
+ 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
+ false /* restricted */, "test-owner", 4 /* cookie */, false /* requireConfirmation */,
+ 9 /* sensorId */, mBiometricLogger, mBiometricContext,
+ true /* isStrongBiometric */,
+ null /* taskStackListener */, mLockoutCache,
+ mUdfpsOverlayController, mSideFpsController,
+ false /* allowBackgroundAuthentication */, mSensorProps);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
new file mode 100644
index 0000000..93cbef1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintDetectClientTest {
+
+ private static final int USER_ID = 8;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void detectNoContext_v1() throws RemoteException {
+ final FingerprintDetectClient client = createClient(1);
+
+ client.start(mCallback);
+
+ verify(mHal).detectInteraction();
+ verify(mHal, never()).detectInteractionWithContext(any());
+ }
+
+ @Test
+ public void detectNoContext_v2() throws RemoteException {
+ final FingerprintDetectClient client = createClient(2);
+
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).detectInteraction();
+ }
+
+ private FingerprintDetectClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintDetectClient(mContext, () -> aidl, mToken,
+ 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
+ "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+ mUdfpsOverlayController, true /* isStrongBiometric */);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
new file mode 100644
index 0000000..5a96f5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.log.CallbackWithProbe;
+import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Consumer;
+
+@Presubmit
+@SmallTest
+public class FingerprintEnrollClientTest {
+
+ private static final byte[] HAT = new byte[69];
+ private static final int USER_ID = 8;
+ private static final int POINTER_ID = 0;
+ private static final int TOUCH_X = 8;
+ private static final int TOUCH_Y = 20;
+ private static final float TOUCH_MAJOR = 4.4f;
+ private static final float TOUCH_MINOR = 5.5f;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+ @Mock
+ private ISession mHal;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mClientMonitorCallbackConverter;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock
+ private ISidefpsController mSideFpsController;
+ @Mock
+ private FingerprintSensorPropertiesInternal mSensorProps;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Sensor.HalSessionCallback mHalSessionCallback;
+ @Mock
+ private Probe mLuxProbe;
+ @Captor
+ private ArgumentCaptor<OperationContext> mOperationContextCaptor;
+ @Captor
+ private ArgumentCaptor<PointerContext> mPointerContextCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setup() {
+ when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i ->
+ new CallbackWithProbe<>(mLuxProbe, i.getArgument(0)));
+ when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer(
+ i -> i.getArgument(0));
+ }
+
+ @Test
+ public void enrollNoContext_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+
+ client.start(mCallback);
+
+ verify(mHal).enroll(any());
+ verify(mHal, never()).enrollWithContext(any(), any());
+ }
+
+ @Test
+ public void enrollWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+
+ client.start(mCallback);
+
+ InOrder order = inOrder(mHal, mBiometricContext);
+ order.verify(mBiometricContext).updateContext(
+ mOperationContextCaptor.capture(), anyBoolean());
+ order.verify(mHal).enrollWithContext(any(), same(mOperationContextCaptor.getValue()));
+ verify(mHal, never()).enroll(any());
+ }
+
+ @Test
+ public void pointerUp_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUp(eq(POINTER_ID));
+ verify(mHal, never()).onPointerUpWithContext(any());
+ }
+
+ @Test
+ public void pointerDown_v1() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(1);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDown(eq(0),
+ eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR));
+ verify(mHal, never()).onPointerDownWithContext(any());
+ }
+
+ @Test
+ public void pointerUpWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerUp();
+
+ verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerUp(eq(POINTER_ID));
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void pointerDownWithContext_v2() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(2);
+ client.start(mCallback);
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+
+ verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture());
+ verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat());
+
+ final PointerContext pContext = mPointerContextCaptor.getValue();
+ assertThat(pContext.pointerId).isEqualTo(POINTER_ID);
+ }
+
+ @Test
+ public void luxProbeWhenFingerDown() throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+ client.start(mCallback);
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe).enable();
+
+ client.onAcquired(2, 0);
+ verify(mLuxProbe, never()).disable();
+
+ client.onPointerUp();
+ verify(mLuxProbe).disable();
+
+ client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
+ verify(mLuxProbe, times(2)).enable();
+ }
+
+ @Test
+ public void notifyHalWhenContextChanges() throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+ client.start(mCallback);
+
+ verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture());
+ OperationContext opContext = mOperationContextCaptor.getValue();
+
+ // fake an update to the context
+ verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
+ mContextInjector.getValue().accept(opContext);
+ verify(mHal).onContextChanged(eq(opContext));
+
+ client.stopHalOperation();
+ verify(mBiometricContext).unsubscribe(same(opContext));
+ }
+
+ @Test
+ public void showHideOverlay_cancel() throws RemoteException {
+ showHideOverlay(c -> c.cancel());
+ }
+
+ @Test
+ public void showHideOverlay_stop() throws RemoteException {
+ showHideOverlay(c -> c.stopHalOperation());
+ }
+
+ @Test
+ public void showHideOverlay_error() throws RemoteException {
+ showHideOverlay(c -> c.onError(0, 0));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void showHideOverlay_result() throws RemoteException {
+ showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
+ }
+
+ private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
+ throws RemoteException {
+ final FingerprintEnrollClient client = createClient();
+
+ client.start(mCallback);
+
+ verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any());
+ verify(mSideFpsController).show(anyInt(), anyInt());
+
+ block.accept(client);
+
+ verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+ verify(mSideFpsController).hide(anyInt());
+ }
+
+ private FingerprintEnrollClient createClient() throws RemoteException {
+ return createClient(500);
+ }
+
+ private FingerprintEnrollClient createClient(int version) throws RemoteException {
+ when(mHal.getInterfaceVersion()).thenReturn(version);
+
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ return new FingerprintEnrollClient(mContext, () -> aidl, mToken, 6 /* requestId */,
+ mClientMonitorCallbackConverter, 0 /* userId */,
+ HAT, "owner", mBiometricUtils, 8 /* sensorId */,
+ mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
+ mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 73f1516..5a1a02e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -40,6 +40,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -71,6 +72,8 @@
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@Mock
private FingerprintStateCallback mFingerprintStateCallback;
+ @Mock
+ private BiometricContext mBiometricContext;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -105,7 +108,7 @@
mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher);
+ mGestureAvailabilityDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -157,9 +160,10 @@
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
- gestureAvailabilityDispatcher);
+ gestureAvailabilityDispatcher, biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index d4609b5..e1a4a2d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -32,6 +32,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.CoexCoordinator;
import com.android.server.biometrics.sensors.LockoutCache;
@@ -66,6 +68,10 @@
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -101,7 +107,8 @@
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
- () -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
+ () -> new AidlSession(1, mSession, USER_ID, mHalCallback),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache,
mLockoutResetDispatcher));
mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index f6b9209..529f994 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
@@ -70,6 +71,8 @@
private BiometricScheduler mScheduler;
@Mock
private FingerprintStateCallback mFingerprintStateCallback;
+ @Mock
+ private BiometricContext mBiometricContext;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -101,7 +104,7 @@
mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
- mHalResultController);
+ mHalResultController, mBiometricContext);
}
@Test
@@ -126,9 +129,10 @@
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller) {
+ @NonNull HalResultController controller,
+ @NonNull BiometricContext biometricContext) {
super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ lockoutResetDispatcher, controller, biometricContext);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ff1b6f6..83fa7ac 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -17,17 +17,23 @@
package com.android.server.companion.virtual;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInputConstants;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -46,18 +52,31 @@
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
private InputController.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private IInputManager mIInputManagerMock;
private InputController mInputController;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = "uniqueId";
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+ InputManager.resetInstance(mIInputManagerMock);
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
mInputController = new InputController(new Object(), mNativeWrapperMock);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e36263e..33540c8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,6 +58,7 @@
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.DisplayInfo;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
@@ -80,6 +82,8 @@
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
+ private static final String UNIQUE_ID = "uniqueid";
+ private static final String PHYS = "phys";
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
@@ -116,6 +120,12 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.uniqueId = UNIQUE_ID;
+ doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+ LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+ LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -150,11 +160,11 @@
mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), anyInt());
+ nullable(String.class), anyInt(), eq(null));
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
}
@Test
@@ -167,7 +177,7 @@
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
}
@Test
@@ -186,7 +196,7 @@
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
@@ -202,7 +212,7 @@
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
- nullable(String.class), eq(displayId));
+ nullable(String.class), eq(displayId), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
// Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
@@ -274,7 +284,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString());
}
@Test
@@ -284,7 +295,8 @@
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+ verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+ anyString());
}
@Test
@@ -294,8 +306,8 @@
BINDER, new Point(WIDTH, HEIGHT));
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
- WIDTH);
+ verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@Test
@@ -315,7 +327,7 @@
final int action = VirtualKeyEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -340,7 +352,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -355,7 +367,7 @@
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -381,7 +393,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build());
@@ -395,7 +407,7 @@
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -422,7 +434,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mInputController.mActivePointerDisplayId = 1;
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
@@ -437,7 +449,7 @@
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
@@ -470,7 +482,7 @@
final int action = VirtualTouchEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -489,7 +501,7 @@
final float majorAxisSize = 10.0f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
- /* displayId= */ 1));
+ /* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
.setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
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..e7bf293 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -28,6 +28,14 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -101,6 +109,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -111,6 +120,7 @@
import android.hardware.usb.UsbManager;
import android.net.ProfileNetworkPreference;
import android.net.Uri;
+import android.net.wifi.WifiSsid;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -138,6 +148,9 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
+import com.android.server.pm.RestrictionsSet;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -152,6 +165,7 @@
import java.io.File;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -282,6 +296,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 +1806,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 +1821,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));
}
@@ -7719,30 +7739,20 @@
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
- int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
initializeDpms();
-
- returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
}
@Test
- public void testSetDeviceOwnerType_asDeviceOwner_throwsExceptionWhenSetDeviceOwnerTypeAgain()
+ public void testSetDeviceOwnerType_asDeviceOwner_setDeviceOwnerTypeTwice_success()
throws Exception {
setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT);
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
- int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1);
- assertThat(dpms.mOwners.hasDeviceOwner()).isTrue();
- assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
-
- assertThrows(IllegalStateException.class,
- () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT));
+ assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED);
}
@Test
@@ -7758,6 +7768,322 @@
}
@Test
+ public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.addUserRestriction(admin1, restriction));
+
+ verify(getServices().userManagerInternal, never())
+ .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean());
+ DpmTestUtils.assertRestrictions(new Bundle(), dpm.getUserRestrictions(admin1));
+ }
+ }
+ }
+
+ @Test
+ public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) {
+ if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) {
+ assertNoDeviceOwnerRestrictions();
+ dpm.addUserRestriction(admin1, restriction);
+
+ Bundle globalRestrictions =
+ dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER);
+ RestrictionsSet localRestrictions = new RestrictionsSet();
+ localRestrictions.updateRestrictions(
+ UserHandle.USER_SYSTEM,
+ dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions(
+ UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
+ verify(getServices().userManagerInternal)
+ .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(globalRestrictions),
+ MockUtils.checkUserRestrictions(
+ UserHandle.USER_SYSTEM, localRestrictions),
+ eq(true));
+ reset(getServices().userManagerInternal);
+
+ DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(restriction),
+ dpm.getUserRestrictions(admin1));
+
+ dpm.clearUserRestriction(admin1, restriction);
+ reset(getServices().userManagerInternal);
+ }
+ }
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet()
+ throws Exception {
+ int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+ | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskFeatures(admin1, validLockTaskFeatures);
+
+ verify(getServices().iactivityTaskManager)
+ .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures));
+ }
+
+ @Test
+ public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
+ throws Exception {
+ int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
+ | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ // Called during setup.
+ verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt());
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures));
+
+ verifyNoMoreInteractions(getServices().iactivityTaskManager);
+ }
+
+ @Test
+ public void testIsUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ipackageManager.getBlockUninstallForUser(
+ eq(packageName), eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetUninstallBlocked_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUninstallBlocked(admin1, packageName, false);
+
+ verify(getServices().ipackageManager)
+ .setBlockUninstallForUser(eq(packageName), eq(false),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
+ List<String> packages = new ArrayList<>();
+ packages.add("com.android.foo.package");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setUserControlDisabledPackages(admin1, packages);
+
+ verify(getServices().packageManagerInternal)
+ .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages));
+ }
+
+ @Test
+ public void testGetUserControlDisabledPackages_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty();
+ }
+
+ @Test
+ public void testSetOrganizationName_financeDo_success() throws Exception {
+ String organizationName = "Test Organization";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setOrganizationName(admin1, organizationName);
+
+ assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName);
+ }
+
+ @Test
+ public void testSetShortSupportMessage_financeDo_success() throws Exception {
+ String supportMessage = "Test short support message";
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setShortSupportMessage(admin1, supportMessage);
+
+ assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage);
+ }
+
+ @Test
+ public void testIsBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+
+ assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue();
+ }
+
+ @Test
+ public void testSetBackupServiceEnabled_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setBackupServiceEnabled(admin1, true);
+
+ verify(getServices().ibackupManager)
+ .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true));
+ }
+
+ @Test
+ public void testIsLockTaskPermitted_financeDo_success() throws Exception {
+ String packageName = "com.android.foo.package";
+ mockPolicyExemptApps(packageName);
+ mockVendorPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isLockTaskPermitted(packageName)).isTrue();
+ }
+
+ @Test
+ public void testSetLockTaskPackages_financeDo_success() throws Exception {
+ String[] packages = {"com.android.foo.package"};
+ mockEmptyPolicyExemptApps();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.setLockTaskPackages(admin1, packages);
+
+ verify(getServices().iactivityManager)
+ .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages));
+ }
+
+ @Test
+ public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
+ IntentFilter filter = new IntentFilter();
+ ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.addPersistentPreferredActivity(admin1, filter, target);
+
+ verify(getServices().ipackageManager)
+ .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
+ String packageName = admin2.getPackageName();
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearPackagePersistentPreferredActivities(admin1, packageName);
+
+ verify(getServices().ipackageManager)
+ .clearPackagePersistentPreferredActivities(
+ eq(packageName), eq(UserHandle.USER_SYSTEM));
+ verify(getServices().ipackageManager)
+ .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testWipeData_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+ when(getServices().userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+ when(mMockContext.getResources()
+ .getString(R.string.work_profile_deleted_description_dpm_wipe))
+ .thenReturn("Test string");
+
+ dpm.wipeData(0);
+
+ verifyRebootWipeUserData(/* wipeEuicc= */ false);
+ }
+
+ @Test
+ public void testIsDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue();
+ }
+
+ @Test
+ public void testClearDeviceOwnerApp_financeDo_success() throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+ assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull();
+ assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse();
+ verify(mMockContext.spiedContext, times(2))
+ .sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(),
+ permission.READ_CALENDAR,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
+ public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package",
+ permission.READ_PHONE_STATE,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED));
+ }
+
+ @Test
+ public void testGetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.getPermissionGrantState(admin1, admin1.getPackageName(),
+ permission.READ_CALENDAR));
+ }
+
+ @Test
+ public void testGetPermissionGrantState_financeDo_notDeviceOwnerPackage_throwsException()
+ throws Exception {
+ setDeviceOwner();
+ dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.getPermissionGrantState(admin1, "com.android.foo.package",
+ permission.READ_PHONE_STATE));
+ }
+
+ @Test
public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
assertThrows(SecurityException.class,
() -> dpm.setUsbDataSignalingEnabled(true));
@@ -8036,7 +8362,8 @@
@Test
public void testSetSsidAllowlist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
- final Set<String> ssids = Collections.singleton("ssid1");
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
}
@@ -8045,7 +8372,8 @@
public void testSetSsidAllowlist_asDeviceOwner() throws Exception {
setDeviceOwner();
- final Set<String> ssids = Collections.singleton("ssid1");
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
@@ -8061,7 +8389,10 @@
configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
mContext.binder.callingUid = managedProfileAdminUid;
- final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8)),
+ WifiSsid.fromBytes("ssid2".getBytes(StandardCharsets.UTF_8)),
+ WifiSsid.fromBytes("ssid3".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
@@ -8073,14 +8404,15 @@
public void testSetSsidAllowlist_emptyList() throws Exception {
setDeviceOwner();
- final Set<String> ssids = new ArraySet<>();
+ final Set<WifiSsid> ssids = new ArraySet<>();
assertThrows(IllegalArgumentException.class,
() -> WifiSsidPolicy.createAllowlistPolicy(ssids));
}
@Test
public void testSetSsidDenylist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
- final Set<String> ssids = Collections.singleton("ssid1");
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
}
@@ -8089,7 +8421,8 @@
public void testSetSsidDenylist_asDeviceOwner() throws Exception {
setDeviceOwner();
- final Set<String> ssids = Collections.singleton("ssid1");
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
@@ -8105,7 +8438,10 @@
configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
mContext.binder.callingUid = managedProfileAdminUid;
- final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
+ final Set<WifiSsid> ssids = new ArraySet<>(
+ Arrays.asList(WifiSsid.fromBytes("ssid1".getBytes(StandardCharsets.UTF_8)),
+ WifiSsid.fromBytes("ssid2".getBytes(StandardCharsets.UTF_8)),
+ WifiSsid.fromBytes("ssid3".getBytes(StandardCharsets.UTF_8))));
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
@@ -8117,7 +8453,7 @@
public void testSetSsidDenylist_emptyList() throws Exception {
setDeviceOwner();
- final Set<String> ssids = new ArraySet<>();
+ final Set<WifiSsid> ssids = new ArraySet<>();
assertThrows(IllegalArgumentException.class,
() -> WifiSsidPolicy.createDenylistPolicy(ssids));
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 15f3ed1..4cb46b4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -109,6 +109,14 @@
public static Bundle checkUserRestrictions(String... keys) {
final Bundle expected = DpmTestUtils.newRestrictions(
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(expected);
+ }
+
+ public static Bundle checkUserRestrictions(Bundle expected) {
+ return createUserRestrictionsBundleMatcher(expected);
+ }
+
+ private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) {
final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
@Override
public boolean matches(Object item) {
@@ -129,6 +137,15 @@
public static RestrictionsSet checkUserRestrictions(int userId, String... keys) {
final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId,
java.util.Objects.requireNonNull(keys));
+ return checkUserRestrictions(userId, expected);
+ }
+
+ public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) {
+ return createUserRestrictionsSetMatcher(userId, expected);
+ }
+
+ private static RestrictionsSet createUserRestrictionsSetMatcher(
+ int userId, RestrictionsSet expected) {
final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() {
@Override
public boolean matches(Object item) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index a8f24ce..533fb2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -26,6 +26,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -76,6 +77,7 @@
private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName(
TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin");
private static final int TEST_USER_ID = 123;
+ private static final String ROLE_HOLDER_PACKAGE_NAME = "test.role.holder.package.name";
private @Mock Resources mResources;
@@ -305,6 +307,26 @@
ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2", "package3");
}
+ @Test
+ public void testGetNonRequiredApps_managedProfile_roleHolder_works() {
+ when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+ setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+ verifyAppsAreNonRequired(
+ ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2");
+ }
+
+ @Test
+ public void testGetNonRequiredApps_managedDevice_roleHolder_works() {
+ when(mInjector.getDeviceManagerRoleHolderPackageName(any()))
+ .thenReturn(ROLE_HOLDER_PACKAGE_NAME);
+ setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME);
+
+ verifyAppsAreNonRequired(
+ ACTION_PROVISION_MANAGED_DEVICE, "package1", "package2");
+ }
+
private void setupRegularModulesWithManagedUser(String... regularModules) {
setupRegularModulesWithMetadata(regularModules, REQUIRED_APP_MANAGED_USER);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 02a8ae8..9a5254d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -93,7 +93,7 @@
assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse();
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_FINANCED);
+ DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
// There is no device owner, so the default owner type should be returned.
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_DEFAULT);
@@ -367,7 +367,7 @@
owners.setDeviceOwnerUserRestrictionsMigrated();
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_FINANCED);
+ DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false);
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
@@ -399,7 +399,7 @@
owners.setProfileOwnerUserRestrictionsMigrated(11);
owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(),
- DEVICE_OWNER_TYPE_DEFAULT);
+ DEVICE_OWNER_TYPE_DEFAULT, /* isAdminTestOnly= */ false);
// The previous device owner type should persist.
assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
@@ -585,7 +585,8 @@
assertThat(owners.getProfileOwnerFile(11).exists()).isTrue();
String previousDeviceOwnerPackageName = owners.getDeviceOwnerPackageName();
- owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED);
+ owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED,
+ /* isAdminTestOnly= */ false);
assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo(
DEVICE_OWNER_TYPE_FINANCED);
owners.setDeviceOwnerProtectedPackages(
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index d2cff0e..fe3034d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -326,7 +326,7 @@
assertEquals(callback.getLastNotifiedInfo().currentState,
OTHER_DEVICE_STATE.getIdentifier());
- mService.getBinderService().cancelRequest(token);
+ mService.getBinderService().cancelStateRequest();
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -378,9 +378,9 @@
mPolicy.resumeConfigureOnce();
flushHandler();
- // First request status is now suspended as there is another pending request.
+ // First request status is now canceled as there is another pending request.
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_SUSPENDED);
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
// Second request status still unknown because the service is still awaiting policy
// callback.
assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
@@ -402,19 +402,19 @@
DEFAULT_DEVICE_STATE.getIdentifier());
// Now cancel the second request to make the first request active.
- mService.getBinderService().cancelRequest(secondRequestToken);
+ mService.getBinderService().cancelStateRequest();
flushHandler();
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
- TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
TestDeviceStateManagerCallback.STATUS_CANCELED);
- assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
- OTHER_DEVICE_STATE.getIdentifier());
+ DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
@@ -656,11 +656,6 @@
}
@Override
- public void onRequestSuspended(IBinder token) {
- mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
- }
-
- @Override
public void onRequestCanceled(IBinder token) {
mLastNotifiedStatus.put(token, STATUS_CANCELED);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b94fc43..2297c91 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -18,7 +18,6 @@
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
-import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -66,7 +65,7 @@
}
@Test
- public void addRequest_suspendExistingRequest() {
+ public void addRequest_cancelExistingRequestThroughNewRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(firstRequest));
@@ -75,92 +74,52 @@
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ 1 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(secondRequest));
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void addRequest_cancelActiveRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelRequest(secondRequest.getToken());
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- }
- @Test
- public void addRequest_cancelSuspendedRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
+ mController.cancelOverrideRequest();
- mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelRequest(firstRequest.getToken());
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleBaseStateChanged() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleBaseStateChanged();
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleProcessDied() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.handleProcessDied(1);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleProcessDied(0);
-
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@@ -170,46 +129,29 @@
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
- 0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.handleProcessDied(1);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelStickyRequests();
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleProcessDied(0);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.cancelStickyRequest();
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
1 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 2 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{ 0, 1 });
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{ 0 });
-
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@@ -217,18 +159,11 @@
public void cancelOverrideRequestsTest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
1 /* requestedState */, 0 /* flags */);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
- 2 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
- mController.addRequest(secondRequest);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
- assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
-
- mController.cancelOverrideRequests();
-
- assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ mController.cancelOverrideRequest();
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 4caa85c..f5a5689 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -81,6 +81,7 @@
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock Handler mNoOpHandler;
@Mock HighBrightnessModeController mHbmController;
+ @Mock BrightnessThrottler mBrightnessThrottler;
@Before
public void setUp() {
@@ -128,12 +129,15 @@
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
- mContext, mHbmController, mIdleBrightnessMappingStrategy,
+ mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
);
when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
+ // Disable brightness throttling by default. Individual tests can enable it as needed.
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(false);
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
@@ -420,4 +424,47 @@
assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
}
+
+ @Test
+ public void testBrightnessGetsThrottled() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ mController = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return max brightness at 100 lux
+ final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
+ final float lux = 100.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+ .thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+ .thenReturn(lux);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness);
+
+ // Sensor reads 100 lux. We should get max brightness.
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux));
+ assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+
+ // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
+ final float throttledBrightness = 0.123f;
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(true);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+ BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+ 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
+
+ // Remove throttling and notify ABC again
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(false);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
+ BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
+ 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 24a4751..f352de4 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -36,8 +36,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import java.util.Arrays;
@@ -147,11 +150,14 @@
private static final float TOLERANCE = 0.0001f;
+ @Mock
+ DisplayWhiteBalanceController mMockDwbc;
+
@Test
public void testSimpleStrategyMappingAtControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 0; i < LUX_LEVELS.length; i++) {
final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
@@ -166,7 +172,7 @@
public void testSimpleStrategyMappingBetweenControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 1; i < LUX_LEVELS.length; i++) {
final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -181,7 +187,7 @@
public void testSimpleStrategyIgnoresNewConfiguration() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
final float[] lux = { 0f, 1f };
final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -196,7 +202,7 @@
public void testSimpleStrategyIgnoresNullConfiguration() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
strategy.setBrightnessConfiguration(null);
final int N = DISPLAY_LEVELS_BACKLIGHT.length;
@@ -210,7 +216,7 @@
public void testPhysicalStrategyMappingAtControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
for (int i = 0; i < LUX_LEVELS.length; i++) {
final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -227,7 +233,7 @@
public void testPhysicalStrategyMappingBetweenControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
Spline brightnessToNits =
Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -244,7 +250,7 @@
public void testPhysicalStrategyUsesNewConfigurations() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
final float[] lux = { 0f, 1f };
final float[] nits = {
@@ -269,7 +275,7 @@
public void testPhysicalStrategyRecalculateSplines() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS);
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -301,7 +307,7 @@
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
}
@@ -314,13 +320,13 @@
lux[idx+1] = tmp;
Resources res = createResources(lux, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
// And make sure we get the same result even if it's monotone but not increasing.
lux[idx] = lux[idx+1];
res = createResources(lux, DISPLAY_LEVELS_NITS);
- strategy = BrightnessMappingStrategy.create(res, ddc);
+ strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
}
@@ -333,11 +339,11 @@
lux[lux.length - 1] = lux[lux.length - 2] + 1;
Resources res = createResources(lux, DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT);
- strategy = BrightnessMappingStrategy.create(res, ddc);
+ strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
// Extra backlight level
@@ -345,14 +351,14 @@
DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1);
backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
res = createResources(LUX_LEVELS, backlight);
- strategy = BrightnessMappingStrategy.create(res, ddc);
+ strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
// Extra nits level
final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1);
nits[nits.length - 1] = nits[nits.length - 2] + 1;
res = createResources(LUX_LEVELS, nits);
- strategy = BrightnessMappingStrategy.create(res, ddc);
+ strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(strategy);
}
@@ -361,17 +367,17 @@
Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(physical);
res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
DISPLAY_LEVELS_NITS);
- physical = BrightnessMappingStrategy.create(res, ddc);
+ physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(physical);
res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
DISPLAY_LEVELS_NITS);
- physical = BrightnessMappingStrategy.create(res, ddc);
+ physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
assertNull(physical);
}
@@ -380,10 +386,10 @@
Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
DISPLAY_LEVELS_NITS);
DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc));
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc));
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
}
@Test
@@ -394,7 +400,7 @@
// Create an idle mode bms
// This will fail if it tries to fetch the wrong configuration.
BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
- null);
+ mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", bms);
// Ensure that the config is the one we set
@@ -586,7 +592,8 @@
Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+ mMockDwbc);
// Let's start with a validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -614,7 +621,8 @@
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+ mMockDwbc);
// Validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -639,7 +647,8 @@
// just make sure the adjustment reflects the change.
Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+ mMockDwbc);
assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
strategy.addUserDataPoint(2500, 1.0f);
assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
@@ -660,7 +669,8 @@
final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS);
DisplayDeviceConfig ddc = createDdc();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+ mMockDwbc);
// Validity, as per tradition:
assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
new file mode 100644
index 0000000..0ed90d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Temperature.ThrottlingStatus;
+import android.os.Temperature;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.BrightnessThrottler.Injector;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BrightnessThrottlerTest {
+ private static final float EPSILON = 0.000001f;
+
+ private Handler mHandler;
+ private TestLooper mTestLooper;
+
+ @Mock IThermalService mThermalServiceMock;
+ @Mock Injector mInjectorMock;
+
+ @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
+ mTestLooper = new TestLooper();
+ mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ return true;
+ }
+ });
+
+ }
+
+ /////////////////
+ // Test Methods
+ /////////////////
+
+ @Test
+ public void testBrightnessThrottlingData() {
+ List<ThrottlingLevel> singleLevel = new ArrayList<>();
+ singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+ List<ThrottlingLevel> validLevels = new ArrayList<>();
+ validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+ validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+ List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
+ unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+ unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+
+ List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
+ unsortedBrightnessLevels.add(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+ unsortedBrightnessLevels.add(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+
+ List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
+ unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+ unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+
+ List<ThrottlingLevel> invalidLevel = new ArrayList<>();
+ invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ PowerManager.BRIGHTNESS_MAX + EPSILON));
+
+ // Test invalid data
+ BrightnessThrottlingData data;
+ data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create((BrightnessThrottlingData)null);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedThermalLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedBrightnessLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(invalidLevel);
+ assertEquals(data, null);
+
+ // Test valid data
+ data = BrightnessThrottlingData.create(singleLevel);
+ assertNotEquals(data, null);
+ assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
+
+ data = BrightnessThrottlingData.create(validLevels);
+ assertNotEquals(data, null);
+ assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
+ }
+
+ @Test
+ public void testThrottlingUnsupported() throws Exception {
+ final BrightnessThrottler throttler = createThrottlerUnsupported();
+ assertFalse(throttler.deviceSupportsThrottling());
+
+ // Thermal listener shouldn't be registered if throttling is unsupported
+ verify(mInjectorMock, never()).getThermalService();
+
+ // Ensure that brightness is uncapped when the device doesn't support throttling
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ }
+
+ @Test
+ public void testThrottlingSingleLevel() throws Exception {
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Set status just high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status more than high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to the lower throttling level
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Cool down
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+ throttler.getBrightnessMaxReason());
+ }
+
+ @Test
+ public void testThrottlingMultiLevel() throws Exception {
+ final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
+ 0.62f);
+ final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(levelLo);
+ levels.add(levelHi);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Set status just high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to an intermediate throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to the highest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to exceed the highest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to an intermediate throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to the lowest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Cool down
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+ }
+
+ private void assertThrottlingLevelsEquals(
+ List<ThrottlingLevel> expected,
+ List<ThrottlingLevel> actual) {
+ assertEquals(expected.size(), actual.size());
+
+ for (int i = 0; i < expected.size(); i++) {
+ ThrottlingLevel expectedLevel = expected.get(i);
+ ThrottlingLevel actualLevel = actual.get(i);
+
+ assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
+ assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
+ }
+ }
+
+ private BrightnessThrottler createThrottlerUnsupported() {
+ return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {});
+ }
+
+ private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
+ assertNotNull(data);
+ return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {});
+ }
+
+ private Temperature getSkinTemp(@ThrottlingStatus int status) {
+ return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 418831f..40c0392 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1461,7 +1461,8 @@
// Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, hbmRefreshRate);
@@ -1469,7 +1470,8 @@
// Turn on HBM, with brightness below the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1477,7 +1479,8 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1485,7 +1488,8 @@
// Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, hbmRefreshRate);
@@ -1493,7 +1497,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1501,7 +1505,8 @@
// Turn on HBM, with brightness below the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1509,7 +1514,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1580,7 +1585,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, initialRefreshRate);
@@ -1598,7 +1603,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1606,7 +1611,7 @@
// Turn HBM on again and ensure the updated vote value stuck
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, updatedRefreshRate);
@@ -1622,7 +1627,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1654,7 +1659,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1662,7 +1667,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1694,7 +1699,7 @@
// Turn on HBM when HBM is supported; expect a valid transition point and a vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.0f);
@@ -1702,7 +1707,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1711,7 +1716,7 @@
// no vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- HBM_TRANSITION_POINT_INVALID));
+ HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1720,7 +1725,7 @@
// no vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
- HBM_TRANSITION_POINT_INVALID));
+ HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1728,7 +1733,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1737,7 +1742,8 @@
private void setHbmAndAssertRefreshRate(
DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
when(mInjector.getBrightnessInfo(DISPLAY_ID))
- .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
+ .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1817,7 +1823,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.f);
@@ -1978,7 +1984,8 @@
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 4bb5d74..53fa3e2 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -16,15 +16,20 @@
package com.android.server.display;
+import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController
.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
+import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static org.junit.Assert.assertEquals;
@@ -109,7 +114,8 @@
private static final HighBrightnessModeData DEFAULT_HBM_DATA =
new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
- THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE);
+ THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE,
+ HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
@Before
public void setUp() {
@@ -134,7 +140,7 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@@ -144,7 +150,7 @@
initHandler(null);
final HighBrightnessModeController hbmc = new HighBrightnessModeController(
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
- mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy);
+ mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy);
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -201,7 +207,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -233,7 +239,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -258,18 +264,18 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
advanceTime(1);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -288,13 +294,13 @@
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
// Go into HBM for half the allowed window
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
// Move lux below threshold (ending first event);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
// Move up some amount of time so that there's still time in the window even after a
@@ -304,7 +310,7 @@
// Go into HBM for just under the second half of allowed window
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 1);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -434,7 +440,7 @@
float brightness = 0.5f;
float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
@@ -442,21 +448,21 @@
brightness = 0.33f;
expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
// Try the min value
brightness = DEFAULT_MIN;
expectedHdrBrightness = DEFAULT_MIN;
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
// Try the max value
brightness = TRANSITION_POINT;
expectedHdrBrightness = DEFAULT_MAX;
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
}
@@ -467,7 +473,7 @@
final int displayStatsId = mDisplayUniqueId.hashCode();
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
- hbmc.onBrightnessChanged(TRANSITION_POINT);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
advanceTime(0);
@@ -489,7 +495,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify Stats HBM_ON_SUNLIGHT
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
@@ -506,12 +512,12 @@
}
@Test
- public void tetHbmStats_NbmHdrNoReport() {
+ public void testHbmStats_NbmHdrNoReport() {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
final int displayStatsId = mDisplayUniqueId.hashCode();
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
- hbmc.onBrightnessChanged(DEFAULT_MIN);
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
advanceTime(0);
@@ -524,7 +530,27 @@
}
@Test
- public void testHbmStats_ThermalOff() throws Exception {
+ public void testHbmStats_HighLuxLowBrightnessNoReport() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+ advanceTime(0);
+ // verify in HBM sunlight mode
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+ // Verify Stats HBM_ON_SUNLIGHT not report
+ verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ anyInt());
+ }
+
+ // Test reporting of thermal throttling when triggered by HighBrightnessModeController's
+ // internal thermal throttling.
+ @Test
+ public void testHbmStats_InternalThermalOff() throws Exception {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
final int displayStatsId = mDisplayUniqueId.hashCode();
@@ -534,7 +560,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(1);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -548,6 +574,37 @@
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
}
+ // Test reporting of thermal throttling when triggered externally through
+ // HighBrightnessModeController.onBrightnessChanged()
+ @Test
+ public void testHbmStats_ExternalThermalOff() throws Exception {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+ final float hbmBrightness = TRANSITION_POINT + 0.01f;
+ final float nbmBrightness = TRANSITION_POINT - 0.01f;
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ // Brightness is unthrottled, HBM brightness granted
+ hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE);
+ advanceTime(1);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
+ hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL);
+ advanceTime(1);
+ // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
+ // However, we expect the HBM state reported by HBMC to be off, since external thermal
+ // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
+ // the HBM transition point.
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+ }
+
@Test
public void testHbmStats_TimeOut() {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
@@ -555,7 +612,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -576,7 +633,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -595,7 +652,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -610,6 +667,30 @@
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
}
+ @Test
+ public void tetHbmStats_LowRequestedBrightness() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+ advanceTime(0);
+ // verify in HBM sunlight mode
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+ // verify HBM_ON_SUNLIGHT
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+ // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog
+ .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+ }
+
private void assertState(HighBrightnessModeController hbmc,
float brightnessMin, float brightnessMax, int hbmMode) {
assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
@@ -626,7 +707,7 @@
initHandler(clock);
return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
- DEFAULT_HBM_DATA, () -> {}, mContextSpy);
+ DEFAULT_HBM_DATA, null, () -> {}, mContextSpy);
}
private void initHandler(OffsettableClock clock) {
@@ -649,4 +730,8 @@
private Temperature getSkinTemp(@ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
+
+ private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
+ hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 0f3742f..ac97911 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -59,6 +59,8 @@
@RunWith(JUnit4.class)
public final class AmbientLuxTest {
+
+ private static final float ALLOWED_ERROR_DELTA = 0.001f;
private static final int AMBIENT_COLOR_TYPE = 20705;
private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
@@ -78,6 +80,8 @@
@Mock private TypedArray mHighLightBiases;
@Mock private TypedArray mAmbientColorTemperatures;
@Mock private TypedArray mDisplayColorTemperatures;
+ @Mock private TypedArray mStrongAmbientColorTemperatures;
+ @Mock private TypedArray mStrongDisplayColorTemperatures;
@Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
@Before
@@ -110,6 +114,12 @@
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceDisplayColorTemperatures))
.thenReturn(mDisplayColorTemperatures);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+ .thenReturn(mStrongAmbientColorTemperatures);
+ when(mResourcesSpy.obtainTypedArray(
+ R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+ .thenReturn(mStrongDisplayColorTemperatures);
when(mResourcesSpy.obtainTypedArray(
R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
@@ -375,6 +385,43 @@
}
@Test
+ public void testStrongMode() {
+ final float lowerBrightness = 10.0f;
+ final float upperBrightness = 50.0f;
+ setBrightnesses(lowerBrightness, upperBrightness);
+ setBiases(0.0f, 1.0f);
+ final int ambientColorTempLow = 6000;
+ final int ambientColorTempHigh = 8000;
+ final int displayColorTempLow = 6400;
+ final int displayColorTempHigh = 7400;
+ setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+ setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+ DisplayWhiteBalanceController controller =
+ DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+ controller.setStrongModeEnabled(true);
+ controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+ for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+ ambientTempFraction += 0.1f) {
+ final float ambientTemp =
+ (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+ + ambientColorTempLow;
+ setEstimatedColorTemperature(controller, ambientTemp);
+ for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+ brightnessFraction += 0.1f) {
+ setEstimatedBrightnessAndUpdate(controller,
+ mix(lowerBrightness, upperBrightness, brightnessFraction));
+ assertEquals(controller.mPendingAmbientColorTemperature,
+ mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+ mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+ brightnessFraction),
+ ALLOWED_ERROR_DELTA);
+ }
+ }
+ }
+
+ @Test
public void testLowLight_DefaultAmbient() throws Exception {
final float lowerBrightness = 10.0f;
final float upperBrightness = 50.0f;
@@ -486,6 +533,14 @@
setFloatArrayResource(mDisplayColorTemperatures, vals);
}
+ private void setStrongAmbientColorTemperatures(float... vals) {
+ setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+ }
+
+ private void setStrongDisplayColorTemperatures(float... vals) {
+ setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+ }
+
private void setFloatArrayResource(TypedArray array, float[] vals) {
when(array.length()).thenReturn(vals.length);
for (int i = 0; i < vals.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index d441143..0028969 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -165,6 +165,17 @@
R.bool.config_cecTvSendStandbyOnSleepDisabled_default);
doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguage_userConfigurable);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageEnabled_allowed);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageEnabled_default);
+ doReturn(true).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageDisabled_allowed);
+ doReturn(false).when(resources).getBoolean(
+ R.bool.config_cecSetMenuLanguageDisabled_default);
+
+ doReturn(true).when(resources).getBoolean(
R.bool.config_cecRcProfileTv_userConfigurable);
doReturn(true).when(resources).getBoolean(
R.bool.config_cecRcProfileTvNone_allowed);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index 85d30a6..8e756ae 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -83,6 +83,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -121,6 +122,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -159,6 +161,7 @@
HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+ HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
@@ -317,8 +320,10 @@
@Test
public void getDefaultStringValue_MultipleDefaults() {
setBooleanResource(R.bool.config_cecPowerControlModeBroadcast_default, true);
- assertThrows(RuntimeException.class,
- () -> new HdmiCecConfig(mContext, mStorageAdapter));
+ HdmiCecConfig hdmiCecConfig = new HdmiCecConfig(mContext, mStorageAdapter);
+ assertThat(hdmiCecConfig.getDefaultStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE))
+ .isEqualTo(HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b6c4bc2..a9812ab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -62,6 +62,23 @@
public class HdmiCecLocalDeviceTvTest {
private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
+ private static final String[] SADS_NOT_TO_QUERY = new String[]{
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+ HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX};
+ private static final HdmiCecMessage SAD_QUERY =
+ HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(ADDR_TV, ADDR_AUDIO_SYSTEM,
+ new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
+ Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2});
+
private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
@@ -136,6 +153,10 @@
mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
mTestLooper.dispatchAll();
mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
+ for (String sad : SADS_NOT_TO_QUERY) {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ sad, HdmiControlManager.QUERY_SAD_DISABLED);
+ }
mNativeWrapper.clearResultMessages();
}
@@ -442,6 +463,7 @@
ADDR_TV,
ADDR_AUDIO_SYSTEM);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SAD_QUERY);
}
@Test
@@ -463,6 +485,7 @@
ADDR_TV,
ADDR_AUDIO_SYSTEM);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SAD_QUERY);
}
@Test
@@ -485,6 +508,7 @@
ADDR_TV,
ADDR_AUDIO_SYSTEM);
assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 7751ef5..c48a974 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -42,6 +42,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
+import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
@@ -747,6 +748,114 @@
}
@Test
+ public void addVendorCommandListener_receiveCallback_VendorCmdNoIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandNoId =
+ HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params);
+ mNativeWrapper.onCecMessage(vendorCommandNoId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+ assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+ assertThat(vendorCmdListener.mHasVendorId).isFalse();
+ }
+
+ @Test
+ public void addVendorCommandListener_receiveCallback_VendorCmdWithIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandWithId =
+ HdmiCecMessageBuilder.buildVendorCommandWithId(
+ sourceAddress, destAddress, vendorId, params);
+ mNativeWrapper.onCecMessage(vendorCommandWithId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue();
+ assertThat(vendorCmdListener.mParamsCorrect).isTrue();
+ assertThat(vendorCmdListener.mHasVendorId).isTrue();
+ }
+
+ @Test
+ public void addVendorCommandListener_noCallback_VendorCmdDiffIdTest() {
+ int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress();
+ int sourceAddress = Constants.ADDR_TV;
+ byte[] params = {0x00, 0x01, 0x02, 0x03};
+ int vendorId = 0x123456;
+ int diffVendorId = 0x345678;
+
+ VendorCommandListener vendorCmdListener =
+ new VendorCommandListener(sourceAddress, destAddress, params, vendorId);
+ mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage vendorCommandWithDiffId =
+ HdmiCecMessageBuilder.buildVendorCommandWithId(
+ sourceAddress, destAddress, diffVendorId, params);
+ mNativeWrapper.onCecMessage(vendorCommandWithDiffId);
+ mTestLooper.dispatchAll();
+ assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse();
+ }
+
+ private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub {
+ boolean mVendorCommandCallbackReceived = false;
+ boolean mParamsCorrect = false;
+ boolean mHasVendorId = false;
+
+ int mSourceAddress;
+ int mDestAddress;
+ byte[] mParams;
+ int mVendorId;
+
+ VendorCommandListener(int sourceAddress, int destAddress, byte[] params, int vendorId) {
+ this.mSourceAddress = sourceAddress;
+ this.mDestAddress = destAddress;
+ this.mParams = params.clone();
+ this.mVendorId = vendorId;
+ }
+
+ @Override
+ public void onReceived(
+ int sourceAddress, int destAddress, byte[] params, boolean hasVendorId) {
+ mVendorCommandCallbackReceived = true;
+ if (mSourceAddress == sourceAddress && mDestAddress == destAddress) {
+ byte[] expectedParams;
+ if (hasVendorId) {
+ // If the command has vendor ID, we have to add it to mParams.
+ expectedParams = new byte[params.length];
+ expectedParams[0] = (byte) ((mVendorId >> 16) & 0xFF);
+ expectedParams[1] = (byte) ((mVendorId >> 8) & 0xFF);
+ expectedParams[2] = (byte) (mVendorId & 0xFF);
+ System.arraycopy(mParams, 0, expectedParams, 3, mParams.length);
+ } else {
+ expectedParams = params.clone();
+ }
+ if (Arrays.equals(expectedParams, params)) {
+ mParamsCorrect = true;
+ }
+ }
+ mHasVendorId = hasVendorId;
+ }
+
+ @Override
+ public void onControlStateChanged(boolean enabled, int reason) {}
+ }
+
+ @Test
public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() {
HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby(
Constants.ADDR_TV,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index e80721a..94cf20f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,10 +51,9 @@
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.NetworkStats.METERED_NO;
import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateWifi;
-import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -205,6 +204,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -229,10 +229,12 @@
private static final int TEST_SUB_ID = 42;
private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
-
- private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
+ private static NetworkTemplate sTemplateWifi = new NetworkTemplate.Builder(MATCH_WIFI)
+ .setWifiNetworkKeys(Set.of(TEST_WIFI_NETWORK_KEY)).build();
private static NetworkTemplate sTemplateCarrierMetered =
- buildTemplateCarrierMetered(TEST_IMSI);
+ new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(TEST_IMSI))
+ .setMeteredness(METERED_YES).build();
/**
* Path on assets where files used by {@link NetPolicyXml} are located.
@@ -1160,11 +1162,12 @@
mPolicyListener.expect().onMeteredIfacesChanged(any());
setNetworkPolicies(new NetworkPolicy(
- sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
+ sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(2), false));
mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
- (2 * MB_IN_BYTES) - 512);
+ DataUnit.MEBIBYTES.toBytes(2) - 512);
}
@Test
@@ -1252,7 +1255,7 @@
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
- mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI));
+ mService.snoozeLimit(sTemplateCarrierMetered);
mService.updateNetworks();
verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
@@ -1955,7 +1958,7 @@
assertEquals("Unexpected number of network policies", 1, policies.length);
NetworkPolicy actualPolicy = policies[0];
assertEquals("Unexpected template match rule in network policies",
- NetworkTemplate.MATCH_WIFI,
+ MATCH_WIFI,
actualPolicy.template.getMatchRule());
assertEquals("Unexpected subscriberIds size in network policies",
actualPolicy.template.getSubscriberIds().size(), 0);
@@ -2026,7 +2029,10 @@
private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes,
long limitBytes, boolean inferred) {
- final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID);
+ // TODO: Refactor this to use sTemplateCarrierMetered.
+ final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(FAKE_SUBSCRIBER_ID))
+ .setMeteredness(METERED_YES).build();
return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes,
limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 7f7c716..2f5993d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,7 +61,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Map;
+import java.util.List;
@SmallTest
@Presubmit
@@ -136,9 +136,10 @@
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
- Map<String, String> services = mApexManager.getApexSystemServices();
+ List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
assertThat(services).hasSize(1);
- assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+ assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
+ .matches("com.android.apex.test.ApexSystemService");
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6c9a60a..ac83642 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -315,18 +315,19 @@
.setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
.build();
- ps1.addOrUpdateSuspension("suspendingPackage1", dialogInfo1, appExtras1, launcherExtras1,
- 0);
- ps1.addOrUpdateSuspension("suspendingPackage2", dialogInfo2, appExtras2, launcherExtras2,
- 0);
+ ps1.modifyUserState(0).putSuspendParams( "suspendingPackage1",
+ SuspendParams.getInstanceOrNull(dialogInfo1, appExtras1, launcherExtras1));
+ ps1.modifyUserState(0).putSuspendParams( "suspendingPackage2",
+ SuspendParams.getInstanceOrNull(dialogInfo2, appExtras2, launcherExtras2));
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
watcher.verifyChangeReported("put package 1");
- ps2.addOrUpdateSuspension("suspendingPackage3", null, appExtras1, null, 0);
+ ps2.modifyUserState(0).putSuspendParams( "suspendingPackage3",
+ SuspendParams.getInstanceOrNull(null, appExtras1, null));
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
watcher.verifyChangeReported("put package 2");
- ps3.removeSuspension("irrelevant", 0);
+ ps3.modifyUserState(0).removeSuspension("irrelevant");
settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
watcher.verifyChangeReported("put package 3");
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
index b621a44..869ac88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java
@@ -70,7 +70,7 @@
import androidx.test.filters.Suppress;
import com.android.frameworks.servicestests.R;
-import com.android.internal.content.PackageHelper;
+import com.android.internal.content.InstallLocationUtils;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -106,11 +106,11 @@
private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
- private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
+ private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO;
- private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
+ private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL;
- private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
+ private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL;
void failStr(String errMsg) {
Log.w(TAG, "errMsg=" + errMsg);
@@ -1214,7 +1214,7 @@
int origDefaultLoc = getDefaultInstallLoc();
InstallParams ip = null;
try {
- setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
// Install first
ip = installFromRawResource("install.apk", rawResId, installFlags, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1303,7 +1303,7 @@
InstallParams ip = null;
try {
PackageManager pm = getPm();
- setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO);
// Install first
ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
@@ -1517,11 +1517,11 @@
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
boolean enable = getUserSettingSetInstallLocation();
if (enable) {
- if (userSetting == PackageHelper.APP_INSTALL_AUTO) {
+ if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) {
iloc = PackageInfo.INSTALL_LOCATION_AUTO;
- } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) {
+ } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) {
iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
- } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) {
+ } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) {
iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
}
}
@@ -1552,7 +1552,7 @@
}
@LargeTest
public void testExistingIUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@@ -1564,14 +1564,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@LargeTest
public void testExistingIUserA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iFlags = PackageManager.INSTALL_INTERNAL;
setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
}
@@ -1616,7 +1616,7 @@
}
@LargeTest
public void testUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@@ -1628,14 +1628,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@LargeTest
public void testUserA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iloc = getExpectedInstallLocation(userSetting);
setUserX(true, userSetting, iloc);
}
@@ -1646,7 +1646,7 @@
*/
@LargeTest
public void testUserPrefOffUserI() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
@@ -1658,14 +1658,14 @@
return;
}
- int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
@LargeTest
public void testUserPrefOffA() throws Exception {
- int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int userSetting = InstallLocationUtils.APP_INSTALL_AUTO;
int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
setUserX(false, userSetting, iloc);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 1e4134e..6c72369 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -41,6 +41,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -323,30 +324,31 @@
}
@Test
public void testPackageUseReasons() throws Exception {
- final PackageStateUnserialized testState1 = new PackageStateUnserialized();
+ PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
+ final PackageStateUnserialized testState1 = new PackageStateUnserialized(packageSetting);
testState1.setLastPackageUsageTimeInMills(-1, 10L);
assertLastPackageUsageUnset(testState1);
- final PackageStateUnserialized testState2 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState2 = new PackageStateUnserialized(packageSetting);
testState2.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT, 20L);
assertLastPackageUsageUnset(testState2);
- final PackageStateUnserialized testState3 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState3 = new PackageStateUnserialized(packageSetting);
testState3.setLastPackageUsageTimeInMills(Integer.MAX_VALUE, 30L);
assertLastPackageUsageUnset(testState3);
- final PackageStateUnserialized testState4 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState4 = new PackageStateUnserialized(packageSetting);
testState4.setLastPackageUsageTimeInMills(0, 40L);
assertLastPackageUsageSet(testState4, 0, 40L);
- final PackageStateUnserialized testState5 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState5 = new PackageStateUnserialized(packageSetting);
testState5.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER, 50L);
assertLastPackageUsageSet(
testState5, PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER, 50L);
- final PackageStateUnserialized testState6 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState6 = new PackageStateUnserialized(packageSetting);
testState6.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT - 1, 60L);
assertLastPackageUsageSet(
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 4a24bbd..8e53ca1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -17,7 +17,7 @@
package com.android.server.pm;
import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC;
-import static android.content.pm.SharedLibraryInfo.TYPE_SDK;
+import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE;
import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
@@ -258,7 +258,7 @@
assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123"));
assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk"));
assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L));
- assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK));
+ assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE));
assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(),
is("ogl.sdk_123"));
assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(),
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
new file mode 100644
index 0000000..4ae9613
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -0,0 +1,410 @@
+/*
+ * 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.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link com.android.server.power.LowPowerStandbyController}.
+ *
+ * Build/Install/Run:
+ * atest LowPowerStandbyControllerTest
+ */
+public class LowPowerStandbyControllerTest {
+ private static final int STANDBY_TIMEOUT = 5000;
+
+ private LowPowerStandbyController mController;
+ private BroadcastInterceptingContext mContextSpy;
+ private Resources mResourcesSpy;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+
+ @Mock
+ private AlarmManager mAlarmManagerMock;
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternalMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+ when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported))
+ .thenReturn(true);
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer.config_lowPowerStandbyNonInteractiveTimeout))
+ .thenReturn(STANDBY_TIMEOUT);
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbyEnabledByDefault))
+ .thenReturn(false);
+
+ FakeSettingsProvider.clearSettingsProvider();
+ MockContentResolver cr = new MockContentResolver(mContextSpy);
+ cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+
+ mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(),
+ () -> mClock.now());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(PowerManagerInternal.class);
+ LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
+ }
+
+ @Test
+ public void testOnSystemReady_isInactivate() {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testActivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ }
+
+ private void awaitStandbyTimeoutAlarm() {
+ ArgumentCaptor<Long> timeArg = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<AlarmManager.OnAlarmListener> listenerArg =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ verify(mAlarmManagerMock).setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ timeArg.capture(), anyString(),
+ listenerArg.capture(), any());
+ mClock.reset();
+ mClock.fastForward(timeArg.getValue());
+ listenerArg.getValue().onAlarm();
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void testOnNonInteractive_notImmediatelyActive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testOnNonInteractive_activateAfterStandbyTimeout() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ awaitStandbyTimeoutAlarm();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true);
+ }
+
+ @Test
+ public void testOnNonInteractive_doesNotActivateWhenBecomingInteractive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+
+ setNonInteractive();
+ advanceTime(STANDBY_TIMEOUT / 2);
+ setInteractive();
+ verifyStandbyAlarmCancelled();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ private void verifyStandbyAlarmCancelled() {
+ InOrder inOrder = inOrder(mAlarmManagerMock);
+ inOrder.verify(mAlarmManagerMock, atLeast(0)).setExact(anyInt(), anyLong(), anyString(),
+ any(), any());
+ inOrder.verify(mAlarmManagerMock).cancel((AlarmManager.OnAlarmListener) any());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testOnInteractive_deactivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setInteractive();
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenance_deactivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ mController.setActiveDuringMaintenance(false);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenance_activeDuringMaintenance_staysActive() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ mController.setActiveDuringMaintenance(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false);
+ }
+
+ @Test
+ public void testOnDozeMaintenanceEnds_activate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(true);
+ setNonInteractive();
+ setDeviceIdleMode(true);
+ awaitStandbyTimeoutAlarm();
+
+ setDeviceIdleMode(false);
+ advanceTime(1000);
+ setDeviceIdleMode(true);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true);
+ }
+
+ @Test
+ public void testLowPowerStandbyDisabled_doesNotActivate() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+ mController.setEnabled(false);
+ setNonInteractive();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any());
+ verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean());
+ }
+
+ @Test
+ public void testLowPowerStandbyEnabled_EnabledChangedBroadcastsAreSent() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(false);
+ futureIntent.assertNotReceived();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(true);
+ assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+ mController.setEnabled(true);
+ futureIntent.assertNotReceived();
+
+ futureIntent = mContextSpy.nextBroadcastIntent(
+ PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
+
+ mController.setEnabled(false);
+ assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+ }
+
+ @Test
+ public void testSetEnabled_WhenNotSupported_DoesNotEnable() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ mController.setEnabled(true);
+
+ assertThat(mController.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void testIsSupported_WhenSupported() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ assertThat(mController.isSupported()).isTrue();
+ }
+
+ @Test
+ public void testIsSupported_WhenNotSupported() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ assertThat(mController.isSupported()).isFalse();
+ }
+
+ @Test
+ public void testAllowlistChange_servicesAreNotified() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ mController.systemReady();
+
+ LowPowerStandbyControllerInternal service = LocalServices.getService(
+ LowPowerStandbyControllerInternal.class);
+ service.addToAllowlist(10);
+ mTestLooper.dispatchAll();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10});
+
+ service.removeFromAllowlist(10);
+ mTestLooper.dispatchAll();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {});
+ }
+
+ @Test
+ public void testForceActive() throws Exception {
+ setLowPowerStandbySupportedConfig(false);
+ mController.systemReady();
+
+ mController.forceActive(true);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isTrue();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true);
+
+ mController.forceActive(false);
+ mTestLooper.dispatchAll();
+
+ assertThat(mController.isActive()).isFalse();
+ verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false);
+ }
+
+ private void setLowPowerStandbySupportedConfig(boolean supported) {
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_lowPowerStandbySupported))
+ .thenReturn(supported);
+ }
+
+ private void setInteractive() throws Exception {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+ }
+
+ private void setNonInteractive() throws Exception {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+ }
+
+ private void setDeviceIdleMode(boolean idle) throws Exception {
+ when(mIPowerManagerMock.isDeviceIdleMode()).thenReturn(idle);
+ mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index d35c679..827349a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.power;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -62,9 +64,11 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
@@ -86,6 +90,7 @@
import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.PowerManagerService.WakeLock;
import com.android.server.power.batterysaver.BatterySaverController;
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -98,6 +103,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -283,6 +289,13 @@
void invalidateIsInteractiveCaches() {
// Avoids an SELinux failure.
}
+
+ @Override
+ LowPowerStandbyController createLowPowerStandbyController(Context context,
+ Looper looper) {
+ return new LowPowerStandbyController(context, mTestLooper.getLooper(),
+ SystemClock::elapsedRealtime);
+ }
});
return mService;
}
@@ -293,6 +306,7 @@
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.removeServiceForTest(BatteryManagerInternal.class);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class);
FakeSettingsProvider.clearSettingsProvider();
}
@@ -467,21 +481,21 @@
// First, ensure that a normal full wake lock does not cause a wakeup
int flags = PowerManager.FULL_WAKE_LOCK;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
// Ensure that the flag does *NOT* work with a partial wake lock.
flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
// Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK
flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
}
@@ -649,12 +663,12 @@
wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
return null;
}).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
- anyInt(), any(), any());
+ anyInt(), any(), any(), any());
doAnswer(inv -> {
wakelockMap.remove((String) inv.getArguments()[1]);
return null;
}).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
- anyInt(), any(), any());
+ anyInt(), any(), any(), any());
//
// TEST STARTS HERE
@@ -667,7 +681,7 @@
// Create a wakelock
mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(wakelockMap.get(tag)).isEqualTo(flags); // Verify wakelock is active.
// Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
@@ -725,7 +739,7 @@
// Take a nap and verify we no longer hold the blocker
int flags = PowerManager.DOZE_WAKE_LOCK;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
mService.getBinderServiceInstance().goToSleep(mClock.now(),
PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
@@ -881,7 +895,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
advanceTime(60);
@@ -907,7 +921,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
advanceTime(1500);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
@@ -983,7 +997,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1023,7 +1037,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY);
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -1064,7 +1078,7 @@
mService.getBinderServiceInstance().acquireWakeLock(token,
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg,
- null /* workSource */, null /* historyTag */, nonDefaultDisplay);
+ null /* workSource */, null /* historyTag */, nonDefaultDisplay, null);
assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
WAKEFULNESS_AWAKE);
@@ -1575,4 +1589,118 @@
.setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue();
verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig));
}
+
+ @Test
+ public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+
+ assertThat(wakeLock.mDisabled).isTrue();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+ mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid});
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ @Test
+ public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
+ createService();
+ mService.systemReady();
+ WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
+ mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
+ mService.setDeviceIdleModeInternal(true);
+ mService.setLowPowerStandbyActiveInternal(true);
+
+ assertThat(wakeLock.mDisabled).isFalse();
+ }
+
+ private WakeLock acquireWakeLock(String tag, int flags) {
+ IBinder token = new Binder();
+ String packageName = "pkg.name";
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
+ null /* callback */);
+ return mService.findWakeLockLocked(token);
+ }
+
+ /**
+ * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback.
+ */
+ @Test
+ public void testNotifyWakeLockCallback() {
+ createService();
+ startSystem();
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder = Mockito.mock(Binder.class);
+ when(callback.asBinder()).thenReturn(callbackBinder);
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback);
+ verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback));
+
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+ verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback));
+ }
+
+ /**
+ * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
+ */
+ @Test
+ public void testNotifyWakeLockCallbackChange() {
+ createService();
+ startSystem();
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+ when(callback1.asBinder()).thenReturn(callbackBinder1);
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+ verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback1));
+
+ final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder2 = Mockito.mock(Binder.class);
+ when(callback2.asBinder()).thenReturn(callbackBinder2);
+ mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2);
+ verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+ anyInt(), anyInt(), any(), any(), same(callback1),
+ anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(),
+ same(callback2));
+
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0);
+ verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
+ anyInt(), any(), any(), same(callback2));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
index 09612e3..a73fcb8 100644
--- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
@@ -211,6 +211,25 @@
dumpLog(log, false));
}
+ @Test
+ public void testAddSystemWakelock() {
+ final int tagDatabaseSize = 6;
+ final int logSize = 10;
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ WakeLockLog log = new WakeLockLog(injectorSpy);
+
+ when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
+ log.onWakeLockAcquired("TagPartial", 101,
+ PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK);
+
+ assertEquals("Wake Lock Log\n"
+ + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n"
+ + " -\n"
+ + " Events: 1, Time-Resets: 0\n"
+ + " Buffer, Bytes used: 3\n",
+ dumpLog(log, false));
+ }
+
private String dumpLog(WakeLockLog log, boolean includeTagDb) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
index a74615d..22d383a 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
@@ -107,6 +107,13 @@
}
}
+ /**
+ * Deletes all messages in queue.
+ */
+ public void clear() {
+ mMessages.clear();
+ }
+
public PriorityQueue<MsgInfo> getPendingMessages() {
return new PriorityQueue<>(mMessages);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 5eed30b..91d4f8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -67,6 +67,7 @@
@Mock Vibrator mVibrator;
private final String callPkg = "com.android.server.notification";
+ private final String sysPkg = "android";
private final int callUid = 10;
private String smsPkg;
private final int smsUid = 11;
@@ -79,6 +80,7 @@
private NotificationRecord mRecordHighCall;
private NotificationRecord mRecordHighCallStyle;
private NotificationRecord mRecordEmail;
+ private NotificationRecord mRecordSystemMax;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
private NotificationRecord mRecordStarredContact;
@@ -191,6 +193,12 @@
mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
+ Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
+ mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg,
+ sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId),
+ "", 1244), getDefaultChannel());
+ mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
+
Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build();
mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
@@ -267,6 +275,7 @@
}
expected.add(mRecordStarredContact);
expected.add(mRecordContact);
+ expected.add(mRecordSystemMax);
expected.add(mRecordEmail);
expected.add(mRecordUrgent);
expected.add(mNoMediaSessionMedia);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7542033..7e27e54 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -66,6 +66,7 @@
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
import android.widget.RemoteViews;
import androidx.test.filters.SmallTest;
@@ -1304,4 +1305,45 @@
assertFalse(record.isConversation());
}
+
+ @Test
+ public void mergePhoneNumbers_nulls() {
+ // make sure nothing dies if we just don't have any phone numbers
+ StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, null /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+ // by default, no phone numbers
+ assertNull(record.getPhoneNumbers());
+
+ // nothing happens if we attempt to merge phone numbers but there aren't any
+ record.mergePhoneNumbers(null);
+ assertNull(record.getPhoneNumbers());
+ }
+
+ @Test
+ public void mergePhoneNumbers_addNumbers() {
+ StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, null /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
+
+ // by default, no phone numbers
+ assertNull(record.getPhoneNumbers());
+
+ // make sure it behaves properly when we merge in some real content
+ record.mergePhoneNumbers(new ArraySet<>(
+ new String[]{"16175551212", "16175552121"}));
+ assertTrue(record.getPhoneNumbers().contains("16175551212"));
+ assertTrue(record.getPhoneNumbers().contains("16175552121"));
+ assertFalse(record.getPhoneNumbers().contains("16175553434"));
+
+ // now merge in a new number, make sure old ones are still there and the new one
+ // is also there
+ record.mergePhoneNumbers(new ArraySet<>(new String[]{"16175553434"}));
+ assertTrue(record.getPhoneNumbers().contains("16175551212"));
+ assertTrue(record.getPhoneNumbers().contains("16175552121"));
+ assertTrue(record.getPhoneNumbers().contains("16175553434"));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index fa294dd..3b67182 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -20,8 +20,8 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.permission.PermissionManager.PERMISSION_GRANTED;
-import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.common.truth.Truth.assertThat;
@@ -130,13 +130,13 @@
@Test
public void testHasPermission() throws Exception {
- when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
+ when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
.thenReturn(PERMISSION_GRANTED);
assertThat(mPermissionHelper.hasPermission(1)).isTrue();
- when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
- .thenReturn(PERMISSION_SOFT_DENIED);
+ when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt()))
+ .thenReturn(PERMISSION_DENIED);
assertThat(mPermissionHelper.hasPermission(1)).isFalse();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index 0bf105d..0552a83 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -19,8 +19,13 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,6 +34,7 @@
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserManager;
@@ -43,6 +49,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -240,6 +248,118 @@
assertFalse(ContentProvider.uriHasUserId(queryUri.getValue()));
}
+ @Test
+ public void testMergePhoneNumbers_noPhoneNumber() {
+ // If merge phone number is called but the contacts lookup turned up no available
+ // phone number (HAS_PHONE_NUMBER is false), then no query should happen.
+
+ // setup of various bits required for querying
+ final Context mockContext = mock(Context.class);
+ final ContentResolver mockContentResolver = mock(ContentResolver.class);
+ when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+ final int contactId = 12345;
+ final Uri lookupUri = Uri.withAppendedPath(
+ ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
+
+ // when the contact is looked up, we return a cursor that has one entry whose info is:
+ // _ID: 1
+ // LOOKUP_KEY: "testlookupkey"
+ // STARRED: 0
+ // HAS_PHONE_NUMBER: 0
+ Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 0);
+ when(mockContentResolver.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ // call searchContacts and then mergePhoneNumbers, make sure we never actually
+ // query the content resolver for a phone number
+ new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+ verify(mockContentResolver, never()).query(
+ eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+ eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+ contains(ContactsContract.Contacts.LOOKUP_KEY),
+ any(), // selection args
+ isNull()); // sort order
+ }
+
+ @Test
+ public void testMergePhoneNumbers_hasNumber() {
+ // If merge phone number is called and the contact lookup has a phone number,
+ // make sure there's then a subsequent query for the phone number.
+
+ // setup of various bits required for querying
+ final Context mockContext = mock(Context.class);
+ final ContentResolver mockContentResolver = mock(ContentResolver.class);
+ when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+ final int contactId = 12345;
+ final Uri lookupUri = Uri.withAppendedPath(
+ ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
+
+ // when the contact is looked up, we return a cursor that has one entry whose info is:
+ // _ID: 1
+ // LOOKUP_KEY: "testlookupkey"
+ // STARRED: 0
+ // HAS_PHONE_NUMBER: 1
+ Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 1);
+
+ // make sure to add some specifics so this cursor is only returned for the
+ // contacts database lookup.
+ when(mockContentResolver.query(eq(lookupUri), any(),
+ isNull(), isNull(), isNull())).thenReturn(cursor);
+
+ // in the case of a phone lookup, return null cursor; that's not an error case
+ // and we're not checking the actual storing of the phone data here.
+ when(mockContentResolver.query(eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+ eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+ contains(ContactsContract.Contacts.LOOKUP_KEY),
+ any(), isNull())).thenReturn(null);
+
+ // call searchContacts and then mergePhoneNumbers, and check that we query
+ // once for the
+ new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
+ verify(mockContentResolver, times(1)).query(
+ eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
+ eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
+ contains(ContactsContract.Contacts.LOOKUP_KEY),
+ eq(new String[] { "testlookupkey" }), // selection args
+ isNull()); // sort order
+ }
+
+ // Creates a cursor that points to one item of Contacts data with the specified
+ // columns.
+ private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
+ Cursor mockCursor = mock(Cursor.class);
+ when(mockCursor.moveToFirst()).thenReturn(true);
+ doAnswer(new Answer<Boolean>() {
+ boolean mAccessed = false;
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ if (!mAccessed) {
+ mAccessed = true;
+ return true;
+ }
+ return false;
+ }
+
+ }).when(mockCursor).moveToNext();
+
+ // id
+ when(mockCursor.getColumnIndex(ContactsContract.Contacts._ID)).thenReturn(0);
+ when(mockCursor.getInt(0)).thenReturn(id);
+
+ // lookup key
+ when(mockCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)).thenReturn(1);
+ when(mockCursor.getString(1)).thenReturn(lookupKey);
+
+ // starred
+ when(mockCursor.getColumnIndex(ContactsContract.Contacts.STARRED)).thenReturn(2);
+ when(mockCursor.getInt(2)).thenReturn(starred);
+
+ // has phone number
+ when(mockCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).thenReturn(3);
+ when(mockCursor.getInt(3)).thenReturn(hasPhone);
+
+ return mockCursor;
+ }
+
private void assertStringArrayEquals(String message, String[] expected, String[] result) {
String expectedString = Arrays.toString(expected);
String resultString = Arrays.toString(result);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index fb15088..abcc8c1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -17,7 +17,6 @@
package com.android.server.notification;
import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
@@ -25,6 +24,7 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
@@ -43,16 +43,20 @@
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
import android.media.AudioAttributes;
+import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.ArraySet;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.server.UiServiceTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -68,10 +72,24 @@
private NotificationMessagingUtil mMessagingUtil;
private ZenModeFiltering mZenModeFiltering;
+ @Mock private TelephonyManager mTelephonyManager;
+
+ private long mTestStartTime;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mZenModeFiltering = new ZenModeFiltering(mContext, mMessagingUtil);
+
+ // for repeat callers / matchesCallFilter
+ mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
+ mTestStartTime = System.currentTimeMillis();
+ }
+
+ @After
+ public void tearDown() {
+ // make sure to get rid of any data stored in repeat callers
+ mZenModeFiltering.cleanUpCallersAfter(mTestStartTime);
}
private NotificationRecord getNotificationRecord() {
@@ -95,6 +113,27 @@
return r;
}
+ private Bundle makeExtrasBundleWithPeople(String[] people) {
+ Bundle extras = new Bundle();
+ extras.putObject(Notification.EXTRA_PEOPLE_LIST, people);
+ return extras;
+ }
+
+ // Create a notification record with the people String array as the
+ // bundled extras, and the numbers ArraySet as additional phone numbers.
+ private NotificationRecord getRecordWithPeopleInfo(String[] people,
+ ArraySet<String> numbers) {
+ // set up notification record
+ NotificationRecord r = mock(NotificationRecord.class);
+ StatusBarNotification sbn = mock(StatusBarNotification.class);
+ Notification notification = mock(Notification.class);
+ notification.extras = makeExtrasBundleWithPeople(people);
+ when(sbn.getNotification()).thenReturn(notification);
+ when(r.getSbn()).thenReturn(sbn);
+ when(r.getPhoneNumbers()).thenReturn(numbers);
+ return r;
+ }
+
@Test
public void testIsMessage() {
NotificationRecord r = getNotificationRecord();
@@ -309,4 +348,150 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_directMatch() {
+ // after calls given an email with an exact string match, make sure that
+ // matchesCallFilter returns the right thing
+ String[] mailSource = new String[]{"mailto:hello.world"};
+ mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // check whether matchesCallFilter returns the right thing
+ Bundle inputMatches = makeExtrasBundleWithPeople(new String[]{"mailto:hello.world"});
+ Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
+ assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ inputMatches, null, 0, 0));
+ assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ inputWrong, null, 0, 0));
+ }
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_telephoneVariants() {
+ // set up telephony manager behavior
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+ String[] telSource = new String[]{"tel:+1-617-555-1212"};
+ mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // cases to test:
+ // - identical number
+ // - same number, different formatting
+ // - different number
+ // - garbage
+ Bundle identical = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+ Bundle same = makeExtrasBundleWithPeople(new String[]{"tel:16175551212"});
+ Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:123-456-7890"});
+ Bundle garbage = makeExtrasBundleWithPeople(new String[]{"asdfghjkl;"});
+
+ assertTrue("identical numbers should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ identical, null, 0, 0));
+ assertTrue("equivalent but non-identical numbers should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same, null, 0, 0));
+ assertFalse("non-equivalent numbers should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different, null, 0, 0));
+ assertFalse("non-tel strings should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ garbage, null, 0, 0));
+ }
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_urlEncodedTels() {
+ // this is not intended to be a supported case but is one that we have seen
+ // sometimes in the wild, so make sure we handle url-encoded telephone numbers correctly
+ // when somebody provides one.
+
+ // set up telephony manager behavior
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+ String[] telSource = new String[]{"tel:%2B16175551212"};
+ mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // test cases for various forms of the same phone number and different ones
+ Bundle same1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+ Bundle same2 = makeExtrasBundleWithPeople(new String[]{"tel:%2B1-617-555-1212"});
+ Bundle same3 = makeExtrasBundleWithPeople(new String[]{"tel:6175551212"});
+ Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
+ Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
+
+ assertTrue("same number 1 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same1, null, 0, 0));
+ assertTrue("same number 2 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same2, null, 0, 0));
+ assertTrue("same number 3 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ same3, null, 0, 0));
+ assertFalse("different number 1 should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different1, null, 0, 0));
+ assertFalse("different number 2 should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different2, null, 0, 0));
+ }
+
+ @Test
+ public void testMatchesCallFilter_repeatCallers_viaRecordPhoneNumbers() {
+ // make sure that phone numbers that are passed in via the NotificationRecord's
+ // cached phone numbers field (from a contact lookup if the record is provided a contact
+ // uri) also get recorded in the repeat callers list.
+
+ // set up telephony manager behavior
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
+
+ String[] contactSource = new String[]{"content://contacts/lookup/uri-here"};
+ ArraySet<String> contactNumbers = new ArraySet<>(
+ new String[]{"1-617-555-1212", "1-617-555-3434"});
+ NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers);
+ record.mergePhoneNumbers(contactNumbers);
+ mZenModeFiltering.recordCall(record);
+
+ // set up policy to only allow repeat callers
+ Policy policy = new Policy(
+ PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
+
+ // both phone numbers should register here
+ Bundle tel1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
+ Bundle tel2 = makeExtrasBundleWithPeople(new String[]{"tel:16175553434"});
+ Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:16175555656"});
+
+ assertTrue("contact number 1 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ tel1, null, 0, 0));
+ assertTrue("contact number 2 should match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ tel2, null, 0, 0));
+ assertFalse("different number should not match",
+ ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ policy, UserHandle.SYSTEM,
+ different, null, 0, 0));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 30ad1f9..3298d11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -85,7 +85,6 @@
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
@@ -3114,7 +3113,7 @@
}
@Test
- public void testInClosingAnimation_doNotHideSurface() {
+ public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
makeWindowVisibleAndDrawn(app);
@@ -3123,16 +3122,45 @@
mDisplayContent.mClosingApps.add(app.mActivityRecord);
mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
- // Update visibility and call to remove window
- app.mActivityRecord.commitVisibility(false, false);
+ // Remove window during transition, so it is requested to hide, but won't be committed until
+ // the transition is finished.
+ app.mActivityRecord.onRemovedFromDisplay();
+
+ assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertTrue(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
app.mActivityRecord.prepareSurfaces();
// Because the app is waiting for transition, it should not hide the surface.
assertTrue(app.mActivityRecord.isSurfaceShowing());
+ }
- // Ensure onAnimationFinished will callback when the closing animation is finished.
- verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
- eq(null));
+ @Test
+ public void testInClosingAnimation_visibilityCommitted_hideSurface() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app);
+
+ // Put the activity in close transition.
+ mDisplayContent.mOpeningApps.clear();
+ mDisplayContent.mClosingApps.add(app.mActivityRecord);
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+ // Commit visibility before start transition.
+ app.mActivityRecord.commitVisibility(false, false);
+
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertFalse(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
+ app.mActivityRecord.prepareSurfaces();
+
+ // Because the app visibility has been committed before the transition start, it should hide
+ // the surface.
+ assertFalse(app.mActivityRecord.isSurfaceShowing());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 7c340ec..8ada971 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -191,7 +191,8 @@
public void onFixedRotationFinished(int displayId) {}
@Override
- public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
+ public void onKeepClearAreasChanged(int displayId, List<Rect> restricted,
+ List<Rect> unrestricted) {}
};
int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 687779d..fb3a626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.window.BackNavigationInfo.typeToString;
import static com.google.common.truth.Truth.assertThat;
@@ -71,7 +72,8 @@
@Test
public void backTypeCrossActivityWhenBackToPreviousActivity() {
Task task = createTopTaskWithActivity();
- mAtm.setFocusedTask(task.mTaskId, createActivityRecord(task));
+ mAtm.setFocusedTask(task.mTaskId,
+ createAppWindow(task, FIRST_APPLICATION_WINDOW, "window").mActivityRecord);
BackNavigationInfo backNavigationInfo =
mBackNavigationController.startBackNavigation(task, new StubTransaction());
assertThat(backNavigationInfo).isNotNull();
@@ -85,7 +87,7 @@
@Test
public void backNavInfoFullyPopulated() {
Task task = createTopTaskWithActivity();
- createActivityRecord(task);
+ createAppWindow(task, FIRST_APPLICATION_WINDOW, "window");
// We need a mock screenshot so
TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController();
@@ -115,6 +117,7 @@
private Task createTopTaskWithActivity() {
Task task = createTask(mDefaultDisplay);
ActivityRecord record = createActivityRecord(task);
+ createWindow(null, FIRST_APPLICATION_WINDOW, record, "window");
when(record.mSurfaceControl.isValid()).thenReturn(true);
mAtm.setFocusedTask(task.mTaskId, record);
return task;
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/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 65b5cf5..dcaa511 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -80,7 +80,6 @@
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
@@ -231,34 +230,6 @@
}
@Test
- public void testTaskOutset() {
- final Task task = createTask(mDisplayContent);
- final int taskOutset = 10;
- spyOn(task);
- doReturn(taskOutset).when(task).getTaskOutset();
- doReturn(true).when(task).inMultiWindowMode();
-
- // Mock the resolved override windowing mode to non-fullscreen
- final WindowConfiguration windowConfiguration =
- task.getResolvedOverrideConfiguration().windowConfiguration;
- spyOn(windowConfiguration);
- doReturn(WINDOWING_MODE_MULTI_WINDOW)
- .when(windowConfiguration).getWindowingMode();
-
- // Prevent adjust task dimensions
- doNothing().when(task).adjustForMinimalTaskDimensions(any(), any(), any());
-
- final Rect taskBounds = new Rect(200, 200, 800, 1000);
- // Update surface position and size by the given bounds.
- task.setBounds(taskBounds);
-
- assertEquals(taskBounds.width() + 2 * taskOutset, task.getLastSurfaceSize().x);
- assertEquals(taskBounds.height() + 2 * taskOutset, task.getLastSurfaceSize().y);
- assertEquals(taskBounds.left - taskOutset, task.getLastSurfacePosition().x);
- assertEquals(taskBounds.top - taskOutset, task.getLastSurfacePosition().y);
- }
-
- @Test
public void testActivityAndTaskGetsProperType() {
final Task task1 = new TaskBuilder(mSupervisor).build();
ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent);
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..27e8d69
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static android.app.ActivityManager.procStateToString;
+
+import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager.ProcessState;
+import android.app.usage.BroadcastResponseStats;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+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<>();
+
+ private AppStandbyInternal mAppStandby;
+
+ BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) {
+ mAppStandby = appStandby;
+ }
+
+ // TODO (206518114): Move all callbacks handling to a handler thread.
+ void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
+ UserHandle targetUser, long idForResponseEvent,
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
+ if (DEBUG_RESPONSE_STATS) {
+ Slog.d(TAG, TextUtils.formatSimple("reportBroadcastDispatchEvent; "
+ + "srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s, state=%s",
+ sourceUid, targetPackage, targetUser, idForResponseEvent,
+ TimeUtils.formatDuration(timestampMs), procStateToString(targetUidProcState)));
+ }
+ if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) {
+ // No need to track the broadcast response state while the target app is
+ // in the foreground.
+ return;
+ }
+ 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) {
+ if (DEBUG_RESPONSE_STATS) {
+ Slog.d(TAG, TextUtils.formatSimple(
+ "reportNotificationEvent; event=<%s>, pkg=%s, usr=%d, ts=%s",
+ notificationEventToString(event), packageName, user.getIdentifier(),
+ TimeUtils.formatDuration(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;
+ }
+ if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) {
+ 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);
+ }
+
+ @NonNull
+ private String notificationEventToString(@NotificationEvent int event) {
+ switch (event) {
+ case NOTIFICATION_EVENT_POSTED:
+ return "posted";
+ case NOTIFICATION_EVENT_UPDATED:
+ return "updated";
+ case NOTIFICATION_EVENT_CANCELLED:
+ return "cancelled";
+ default:
+ return String.valueOf(event);
+ }
+ }
+
+ 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..4a761a7 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -29,21 +29,27 @@
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.ActivityManager.ProcessState;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
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;
@@ -84,6 +90,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -136,6 +143,7 @@
= SystemProperties.getBoolean("persist.debug.time_correction", true);
static final boolean DEBUG = false; // Never submit with true
+ static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
@@ -217,6 +225,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 +273,7 @@
}
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onStart() {
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
@@ -271,6 +282,7 @@
mHandler = new H(BackgroundThread.get().getLooper());
mAppStandby = mInjector.getAppStandbyController(getContext());
+ mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby);
mAppTimeLimit = new AppTimeLimitController(getContext(),
new AppTimeLimitController.TimeLimitCallbackListener() {
@@ -315,6 +327,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 +551,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 +561,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 +1810,11 @@
}
return;
}
+ } else if ("broadcast-response-stats".equals(arg)) {
+ synchronized (mLock) {
+ mResponseStatsTracker.dump(idpw);
+ }
+ return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
pkgs.add(arg);
@@ -1813,6 +1848,9 @@
idpw.println();
mAppTimeLimit.dump(null, pw);
+
+ idpw.println();
+ mResponseStatsTracker.dump(idpw);
}
mAppStandby.dumpUsers(idpw, userIds, pkgs);
@@ -2645,6 +2683,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,
@@ -2952,22 +3044,27 @@
@Override
public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
@NonNull UserHandle targetUser, long idForResponseEvent,
- @ElapsedRealtimeLong long timestampMs) {
+ @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) {
+ mResponseStatsTracker.reportBroadcastDispatchEvent(sourceUid, targetPackage,
+ targetUser, idForResponseEvent, timestampMs, targetUidProcState);
}
@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 +3077,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..337e1f9 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -17,6 +17,7 @@
package com.android.server.usb;
import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.os.RemoteException;
@@ -41,6 +42,7 @@
private final boolean mIsInputHeadset;
private final boolean mIsOutputHeadset;
+ private final boolean mIsDock;
private boolean mSelected = false;
private int mOutputState;
@@ -53,7 +55,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 +64,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 +101,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 +146,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 +200,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
@@ -203,24 +214,25 @@
int outputState = (enable && connected) ? 1 : 0;
if (outputState != mOutputState) {
mOutputState = outputState;
- mAudioService.setWiredDeviceConnectionState(device, outputState,
- alsaCardDeviceString,
- mDeviceName, TAG);
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
+ alsaCardDeviceString, mDeviceName);
+ mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
}
}
// Input Device
if (mHasInput) {
- int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET
+ int device = mIsInputHeadset
+ ? AudioSystem.DEVICE_IN_USB_HEADSET
: AudioSystem.DEVICE_IN_USB_DEVICE;
boolean connected = isInputJackConnected();
Slog.i(TAG, "INPUT JACK connected: " + connected);
int inputState = (enable && connected) ? 1 : 0;
if (inputState != mInputState) {
mInputState = inputState;
- mAudioService.setWiredDeviceConnectionState(
- device, inputState, alsaCardDeviceString,
- mDeviceName, TAG);
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
+ alsaCardDeviceString, mDeviceName);
+ mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
}
}
} catch (RemoteException e) {
@@ -231,7 +243,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 +285,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 +297,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 +314,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/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 90ccec8..a061618 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -69,6 +69,7 @@
import com.android.server.LocalServices;
import com.android.server.am.AssistDataRequester;
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+import com.android.server.power.LowPowerStandbyControllerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityAssistInfo;
@@ -89,6 +90,11 @@
static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
System.getProperty("vendor.powerhal.interaction.max", "200"));
static final int BOOST_TIMEOUT_MS = 300;
+ /**
+ * The maximum time an app can stay on the Low Power Standby allowlist when
+ * the session is shown. There to safeguard against apps that don't call hide.
+ */
+ private static final int LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS = 120_000;
// TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
// in the future.
static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
@@ -124,6 +130,10 @@
Executors.newSingleThreadScheduledExecutor();
private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
private final PowerManagerInternal mPowerManagerInternal;
+ private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
+ private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
+ this::removeFromLowPowerStandbyAllowlist;
+ private boolean mLowPowerStandbyAllowlisted;
private PowerBoostSetter mSetPowerBoostRunnable;
private final Handler mFgHandler;
@@ -211,6 +221,8 @@
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mLowPowerStandbyControllerInternal = LocalServices.getService(
+ LowPowerStandbyControllerInternal.class);
mAppOps = context.getSystemService(AppOpsManager.class);
mFgHandler = FgThread.getHandler();
mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
@@ -322,6 +334,15 @@
mSetPowerBoostRunnable = new PowerBoostSetter(
Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
mFgHandler.post(mSetPowerBoostRunnable);
+
+ if (mLowPowerStandbyControllerInternal != null) {
+ mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid);
+ mLowPowerStandbyAllowlisted = true;
+ mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+ mFgHandler.postDelayed(mRemoveFromLowPowerStandbyAllowlistRunnable,
+ LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS);
+ }
+
mCallback.onSessionShown(this);
return true;
}
@@ -493,6 +514,9 @@
}
// A negative value indicates canceling previous boost.
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+ if (mLowPowerStandbyControllerInternal != null) {
+ removeFromLowPowerStandbyAllowlist();
+ }
mCallback.onSessionHidden(this);
}
if (mFullyBound) {
@@ -730,6 +754,16 @@
}
}
+ private void removeFromLowPowerStandbyAllowlist() {
+ synchronized (mLock) {
+ if (mLowPowerStandbyAllowlisted) {
+ mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable);
+ mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid);
+ mLowPowerStandbyAllowlisted = false;
+ }
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
diff --git a/services/wallpapereffectsgeneration/Android.bp b/services/wallpapereffectsgeneration/Android.bp
new file mode 100644
index 0000000..4dbb0fd
--- /dev/null
+++ b/services/wallpapereffectsgeneration/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.wallpapereffectsgeneration-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.wallpapereffectsgeneration",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.wallpapereffectsgeneration-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
new file mode 100644
index 0000000..c228daf
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.wallpapereffectsgeneration.IWallpaperEffectsGenerationService;
+import android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the
+ * {@link android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService}
+ * implementation in another process.
+ */
+public class RemoteWallpaperEffectsGenerationService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteWallpaperEffectsGenerationService,
+ IWallpaperEffectsGenerationService> {
+
+ private static final String TAG =
+ RemoteWallpaperEffectsGenerationService.class.getSimpleName();
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteWallpaperEffectsGenerationServiceCallback mCallback;
+
+ public RemoteWallpaperEffectsGenerationService(Context context,
+ ComponentName componentName, int userId,
+ RemoteWallpaperEffectsGenerationServiceCallback callback,
+ boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, WallpaperEffectsGenerationService.SERVICE_INTERFACE,
+ componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected IWallpaperEffectsGenerationService getServiceInterface(IBinder service) {
+ return IWallpaperEffectsGenerationService.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<IWallpaperEffectsGenerationService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(
+ @NonNull AsyncRequest<IWallpaperEffectsGenerationService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Notifies server (WallpaperEffectsGenerationPerUserService) about unexpected events..
+ */
+ public interface RemoteWallpaperEffectsGenerationServiceCallback
+ extends VultureCallback<RemoteWallpaperEffectsGenerationService> {
+ /**
+ * 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/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
new file mode 100644
index 0000000..0d0b3e0
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapereffectsgeneration;
+
+import static android.Manifest.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.WALLPAPER_EFFECTS_GENERATION_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.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+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 wallpaper effect given a request.
+ */
+public class WallpaperEffectsGenerationManagerService extends
+ AbstractMasterSystemService<WallpaperEffectsGenerationManagerService,
+ WallpaperEffectsGenerationPerUserService> {
+ private static final String TAG =
+ WallpaperEffectsGenerationManagerService.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 WallpaperEffectsGenerationManagerService(Context context) {
+ super(context,
+ new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultWallpaperEffectsGenerationService),
+ null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected WallpaperEffectsGenerationPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new WallpaperEffectsGenerationPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(WALLPAPER_EFFECTS_GENERATION_SERVICE,
+ new WallpaperEffectsGenerationManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final WallpaperEffectsGenerationPerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class WallpaperEffectsGenerationManagerStub
+ extends IWallpaperEffectsGenerationManager.Stub {
+ @Override
+ public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
+ @NonNull ICinematicEffectListener listener) {
+ if (!runForUserLocked("generateCinematicEffect", (service) ->
+ service.onGenerateCinematicEffectLocked(request, listener))) {
+ try {
+ listener.onCinematicEffectGenerated(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+ request.getTaskId()).build());
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "fail to invoke cinematic effect listener for task["
+ + request.getTaskId() + "]");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
+ runForUserLocked("returnCinematicResponse", (service) ->
+ service.onReturnCinematicEffectResponseLocked(response));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new WallpaperEffectsGenerationManagerServiceShellCommand(
+ WallpaperEffectsGenerationManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ /**
+ * Execute the operation for the user locked. Return true if
+ * WallpaperEffectsGenerationPerUserService is found for the user.
+ * Otherwise return false.
+ */
+ private boolean runForUserLocked(@NonNull final String func,
+ @NonNull final Consumer<WallpaperEffectsGenerationPerUserService> 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_WALLPAPER_EFFECTS_GENERATION)
+ == 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();
+ boolean accepted = false;
+ try {
+ synchronized (mLock) {
+ final WallpaperEffectsGenerationPerUserService service =
+ getServiceForUserLocked(userId);
+ if (service != null) {
+ accepted = true;
+ c.accept(service);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return accepted;
+ }
+ }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
new file mode 100644
index 0000000..fc6f75f
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerServiceShellCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapereffectsgeneration;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the WallpaperEffectsGenerationService.
+ */
+public class WallpaperEffectsGenerationManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ WallpaperEffectsGenerationManagerServiceShellCommand.class.getSimpleName();
+
+ private final WallpaperEffectsGenerationManagerService mService;
+
+ public WallpaperEffectsGenerationManagerServiceShellCommand(
+ @NonNull WallpaperEffectsGenerationManagerService 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("WallpaperEffectsGenerationService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("WallpaperEffectsGenerationService 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("WallpaperEffectsGenerationService 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/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
new file mode 100644
index 0000000..d541051
--- /dev/null
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.wallpapereffectsgeneration;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
+import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
+import android.app.wallpapereffectsgeneration.ICinematicEffectListener;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link WallpaperEffectsGenerationManagerService}.
+ */
+public class WallpaperEffectsGenerationPerUserService extends
+ AbstractPerUserSystemService<WallpaperEffectsGenerationPerUserService,
+ WallpaperEffectsGenerationManagerService> implements
+ RemoteWallpaperEffectsGenerationService.RemoteWallpaperEffectsGenerationServiceCallback {
+
+ private static final String TAG =
+ WallpaperEffectsGenerationPerUserService.class.getSimpleName();
+
+ @GuardedBy("mLock")
+ private CinematicEffectListenerWrapper mCinematicEffectListenerWrapper;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteWallpaperEffectsGenerationService mRemoteService;
+
+ protected WallpaperEffectsGenerationPerUserService(
+ WallpaperEffectsGenerationManagerService 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);
+ }
+ if (!Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "WallpaperEffectsGenerationService from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE);
+ }
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ updateRemoteServiceLocked();
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new cinematic effect generation request.
+ */
+ @GuardedBy("mLock")
+ public void onGenerateCinematicEffectLocked(
+ @NonNull CinematicEffectRequest cinematicEffectRequest,
+ @NonNull ICinematicEffectListener cinematicEffectListener) {
+ String newTaskId = cinematicEffectRequest.getTaskId();
+ // Previous request is still being processed.
+ if (mCinematicEffectListenerWrapper != null) {
+ if (mCinematicEffectListenerWrapper.mTaskId.equals(newTaskId)) {
+ invokeCinematicListenerAndCleanup(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
+ .build()
+ );
+ } else {
+ invokeCinematicListenerAndCleanup(
+ new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
+ newTaskId).build()
+ );
+ }
+ return;
+ }
+ RemoteWallpaperEffectsGenerationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.executeOnResolvedService(
+ s -> s.onGenerateCinematicEffect(cinematicEffectRequest));
+ mCinematicEffectListenerWrapper =
+ new CinematicEffectListenerWrapper(newTaskId, cinematicEffectListener);
+ } else {
+ if (isDebug()) {
+ Slog.d(TAG, "Remote service not found");
+ }
+ try {
+ cinematicEffectListener.onCinematicEffectGenerated(
+ createErrorCinematicEffectResponse(newTaskId));
+ } catch (RemoteException e) {
+ if (isDebug()) {
+ Slog.d(TAG, "Failed to invoke cinematic effect listener for task [" + newTaskId
+ + "]");
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies the service of a generated cinematic effect response.
+ */
+ @GuardedBy("mLock")
+ public void onReturnCinematicEffectResponseLocked(
+ @NonNull CinematicEffectResponse cinematicEffectResponse) {
+ invokeCinematicListenerAndCleanup(cinematicEffectResponse);
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ // End existing response and clean up listener for next request.
+ if (mCinematicEffectListenerWrapper != null) {
+ invokeCinematicListenerAndCleanup(
+ createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+ }
+
+ }
+
+ 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;
+ mRemoteService = ensureRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ // Clean up listener for next request.
+ if (mCinematicEffectListenerWrapper != null) {
+ invokeCinematicListenerAndCleanup(
+ createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
+ }
+ }
+
+ private CinematicEffectResponse createErrorCinematicEffectResponse(String taskId) {
+ return new CinematicEffectResponse.Builder(
+ CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR,
+ taskId).build();
+ }
+
+ @GuardedBy("mLock")
+ private void invokeCinematicListenerAndCleanup(
+ CinematicEffectResponse cinematicEffectResponse) {
+ try {
+ if (mCinematicEffectListenerWrapper != null
+ && mCinematicEffectListenerWrapper.mListener != null) {
+ mCinematicEffectListenerWrapper.mListener.onCinematicEffectGenerated(
+ cinematicEffectResponse);
+ } else {
+ if (isDebug()) {
+ Slog.w(TAG, "Cinematic effect listener not found for task["
+ + mCinematicEffectListenerWrapper.mTaskId + "]");
+ }
+ }
+ } catch (RemoteException e) {
+ if (isDebug()) {
+ Slog.w(TAG, "Error invoking cinematic effect listener for task["
+ + mCinematicEffectListenerWrapper.mTaskId + "]");
+ }
+ } finally {
+ mCinematicEffectListenerWrapper = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteWallpaperEffectsGenerationService ensureRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "ensureRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteWallpaperEffectsGenerationService(getContext(),
+ serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ @Override // from RemoteWallpaperEffectsGenerationService
+ public void onServiceDied(RemoteWallpaperEffectsGenerationService service) {
+ Slog.w(TAG, "remote wallpaper effects generation service died");
+ updateRemoteServiceLocked();
+ }
+
+ @Override // from RemoteWallpaperEffectsGenerationService
+ public void onConnectedStateChanged(boolean connected) {
+ if (!connected) {
+ Slog.w(TAG, "remote wallpaper effects generation service disconnected");
+ updateRemoteServiceLocked();
+ }
+ }
+
+ private static final class CinematicEffectListenerWrapper {
+ @NonNull
+ private final String mTaskId;
+ @NonNull
+ private final ICinematicEffectListener mListener;
+
+ CinematicEffectListenerWrapper(
+ @NonNull final String taskId,
+ @NonNull final ICinematicEffectListener listener) {
+ mTaskId = taskId;
+ mListener = listener;
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index c5fc436..27d423b 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -3156,15 +3157,27 @@
}
/**
- * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
- * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
- * call created using
- * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
+ * Calls of this type are created using
+ * {@link TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)}. Unknown calls
+ * are used for representing calls which become known to the {@link ConnectionService}
+ * midway through the call.
+ *
+ * For example, a call transferred from one device to answer would surface as an active
+ * call in Telecom instead of going through a typical Ringing to Active transition, or
+ * Dialing to Active transition.
+ *
+ * A {@link ConnectionService} can return {@code null} (the default behavior)
+ * if it is not able to handle a request for the requested unknown connection.
+ *
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
*
* @hide
*/
- public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
- ConnectionRequest request) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public @Nullable Connection onCreateUnknownConnection(
+ @NonNull PhoneAccountHandle connectionManagerPhoneAccount,
+ @NonNull ConnectionRequest request) {
return null;
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 3b9d3d9..2141c794 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -356,13 +356,17 @@
"android.telecom.extra.INCOMING_CALL_EXTRAS";
/**
- * Optional extra for {@link #ACTION_INCOMING_CALL} containing a boolean to indicate that the
- * call has an externally generated ringer. Used by the HfpClientConnectionService when In Band
- * Ringtone is enabled to prevent two ringers from being generated.
+ * Optional extra for {@link #addNewIncomingCall(PhoneAccountHandle, Bundle)} used to indicate
+ * that a call has an in-band ringtone associated with it. This is used when the device is
+ * acting as an HFP headset and the Bluetooth stack has received an in-band ringtone from the
+ * the HFP host which must be played instead of any local ringtone the device would otherwise
+ * have generated.
+ *
* @hide
*/
- public static final String EXTRA_CALL_EXTERNAL_RINGER =
- "android.telecom.extra.CALL_EXTERNAL_RINGER";
+ @SystemApi
+ public static final String EXTRA_CALL_HAS_IN_BAND_RINGTONE =
+ "android.telecom.extra.CALL_HAS_IN_BAND_RINGTONE";
/**
* Optional extra for {@link android.content.Intent#ACTION_CALL} and
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index e88106c..86b98f1 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -127,7 +127,9 @@
ApnSetting.TYPE_EMERGENCY,
ApnSetting.TYPE_MCX,
ApnSetting.TYPE_XCAP,
- // ApnSetting.TYPE_ENTERPRISE
+ ApnSetting.TYPE_BIP,
+ ApnSetting.TYPE_VSIM,
+ ApnSetting.TYPE_ENTERPRISE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -707,6 +709,9 @@
NetworkCapabilities.NET_CAPABILITY_VSIM,
NetworkCapabilities.NET_CAPABILITY_BIP,
NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT,
+ NetworkCapabilities.NET_CAPABILITY_MMTEL,
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY,
+ NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH
})
public @interface NetCapability { }
@@ -721,4 +726,16 @@
NetworkAgent.VALIDATION_STATUS_NOT_VALID
})
public @interface ValidationStatus {}
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
+ NetworkCapabilities.NET_ENTERPRISE_ID_1,
+ NetworkCapabilities.NET_ENTERPRISE_ID_2,
+ NetworkCapabilities.NET_ENTERPRISE_ID_3,
+ NetworkCapabilities.NET_ENTERPRISE_ID_4,
+ NetworkCapabilities.NET_ENTERPRISE_ID_5
+ })
+
+ public @interface EnterpriseId {}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7ba4b11..3c277b7 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4540,9 +4540,7 @@
* Passing this value as {@link #KEY_SUBSCRIPTION_GROUP_UUID_STRING} will remove the
* subscription from a group instead of adding it to a group.
*
- * TODO: Expose in a future release.
- *
- * @hide
+ * <p>This value will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
*/
public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
@@ -4555,9 +4553,7 @@
* <p>If set to {@link #REMOVE_GROUP_UUID_STRING}, then the subscription will be removed from
* its current group.
*
- * TODO: unhide this key.
- *
- * @hide
+ * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
*/
public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING =
"subscription_group_uuid_string";
@@ -4605,17 +4601,15 @@
"data_switch_validation_min_gap_long";
/**
- * A boolean property indicating whether this subscription should be managed as an opportunistic
- * subscription.
- *
- * If true, then this subscription will be selected based on available coverage and will not be
- * available for a user in settings menus for selecting macro network providers. If unset,
- * defaults to “false”.
- *
- * TODO: unhide this key.
- *
- * @hide
- */
+ * A boolean property indicating whether this subscription should be managed as an opportunistic
+ * subscription.
+ *
+ * If true, then this subscription will be selected based on available coverage and will not be
+ * available for a user in settings menus for selecting macro network providers. If unset,
+ * defaults to “false”.
+ *
+ * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}.
+ */
public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL =
"is_opportunistic_subscription_bool";
@@ -8898,8 +8892,8 @@
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
"capabilities=eims, retry_interval=1000, maximum_retries=20",
- "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
- + "2254, maximum_retries=0", // No retry for those causes
+ "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|"
+ + "2253|2254, maximum_retries=0", // No retry for those causes
"capabilities=mms|supl|cbs, retry_interval=2000",
"capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+ "5000|10000|15000|20000|40000|60000|120000|240000|"
@@ -8920,7 +8914,7 @@
sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY,
- new String[]{"ia", "default", "mms", "dun"});
+ new String[]{"ia", "default"});
sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false);
sDefaults.putBoolean(KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL, false);
sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true);
diff --git a/telephony/java/android/telephony/ImsiEncryptionInfo.java b/telephony/java/android/telephony/ImsiEncryptionInfo.java
index 4978692..82333a4 100644
--- a/telephony/java/android/telephony/ImsiEncryptionInfo.java
+++ b/telephony/java/android/telephony/ImsiEncryptionInfo.java
@@ -46,16 +46,17 @@
private final int keyType;
//Date-Time in UTC when the key will expire.
private final Date expirationTime;
+ private final int carrierId;
/** @hide */
public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier,
- byte[] key, Date expirationTime) {
- this(mcc, mnc, keyType, keyIdentifier, makeKeyObject(key), expirationTime);
+ byte[] key, Date expirationTime, int carrierId) {
+ this(mcc, mnc, keyType, keyIdentifier, makeKeyObject(key), expirationTime, carrierId);
}
/** @hide */
public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier,
- PublicKey publicKey, Date expirationTime) {
+ PublicKey publicKey, Date expirationTime, int carrierId) {
// todo need to validate that ImsiEncryptionInfo is being created with the correct params.
// Including validating that the public key is in "X.509" format. This will be done in
// a subsequent CL.
@@ -65,6 +66,7 @@
this.publicKey = publicKey;
this.keyIdentifier = keyIdentifier;
this.expirationTime = expirationTime;
+ this.carrierId = carrierId;
}
/** @hide */
@@ -78,6 +80,7 @@
keyIdentifier = in.readString();
keyType = in.readInt();
expirationTime = new Date(in.readLong());
+ carrierId = in.readInt();
}
/** @hide */
@@ -90,6 +93,11 @@
return this.mcc;
}
+ /** @hide */
+ public int getCarrierId() {
+ return carrierId;
+ }
+
/**
* Returns key identifier, a string that helps the authentication server to locate the
* private key to decrypt the permanent identity, or {@code null} when uavailable.
@@ -157,6 +165,7 @@
dest.writeString(keyIdentifier);
dest.writeInt(keyType);
dest.writeLong(expirationTime.getTime());
+ dest.writeInt(carrierId);
}
@Override
@@ -164,10 +173,11 @@
return "[ImsiEncryptionInfo "
+ "mcc=" + mcc
+ " mnc=" + mnc
- + " publicKey=" + publicKey
+ + ", publicKey=" + publicKey
+ ", keyIdentifier=" + keyIdentifier
+ ", keyType=" + keyType
+ ", expirationTime=" + expirationTime
+ + ", carrier_id=" + carrierId
+ "]";
}
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 5a12865..e0145e6 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -79,7 +79,7 @@
public static final int ENCODING_8BIT = 2;
public static final int ENCODING_16BIT = 3;
/**
- * @hide This value is not defined in global standard. Only in Korea, this is used.
+ * This value is not defined in global standard. Only in Korea, this is used.
*/
public static final int ENCODING_KSC5601 = 4;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index edb817e..ba1a6ed 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9199,7 +9199,8 @@
* @param allowedNetworkTypes The bitmask of allowed network types.
* @return true on success; false on any failure.
* @hide
- * @deprecated Use {@link #setAllowedNetworkTypesForReason} instead.
+ * @deprecated Use {@link #setAllowedNetworkTypesForReason} instead with reason
+ * {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}.
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9233,10 +9234,7 @@
/**
* To indicate allowed network type change is requested by user.
- *
- * @hide
*/
- @SystemApi
public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0;
/**
@@ -9255,10 +9253,7 @@
* Carrier configuration won't affect the settings configured through
* other reasons and will result in allowing network types that are in both
* configurations (i.e intersection of both sets).
- *
- * @hide
*/
- @SystemApi
public static final int ALLOWED_NETWORK_TYPES_REASON_CARRIER = 2;
/**
@@ -9272,35 +9267,23 @@
/**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
+ * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or
+ * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
* This can be called for following reasons
* <ol>
* <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
- * <li>Allowed network types control by power manager
- * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER}
* <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
- * <li>Allowed network types control by the user-controlled "Allow 2G" toggle
- * {@link #ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
* including the configuration done through other reasons.
*
- * The functionality of this API with the parameter
- * {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER} is the same as the API
- * {@link TelephonyManager#setAllowedNetworkTypes}. Use this API instead of
- * {@link TelephonyManager#setAllowedNetworkTypes}.
- * <p>
- * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
- * ({@link TelephonyManager#CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK}) returns true, then
- * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
- * setPreferredNetworkTypesBitmap is used instead.
- *
* @param reason the reason the allowed network type change is taking place
- * @param allowedNetworkTypes The bitmask of allowed network types.
+ * @param allowedNetworkTypes The bitmask of allowed network type
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
- * @hide
+ * @throws SecurityException if the caller does not have the required privileges
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(
enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
@@ -9330,18 +9313,19 @@
*
* {@link #getAllowedNetworkTypesForReason} returns allowed network type for a
* specific reason.
+ * <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE or
+ * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @param reason the reason the allowed network type change is taking place
* @return the allowed network type bitmask
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
- * @hide
+ * @throws SecurityException if the caller does not have the required permission/privileges
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(
enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
value = TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)
- @SystemApi
public @NetworkTypeBitMask long getAllowedNetworkTypesForReason(
@AllowedNetworkTypesReason int reason) {
if (!isValidAllowedNetworkTypesReason(reason)) {
@@ -11965,7 +11949,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;
}
@@ -13556,127 +13544,88 @@
// 2G
/**
* network type bitmask unknown.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L;
/**
* network type bitmask indicating the support of radio tech GSM.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_GSM = (1 << (NETWORK_TYPE_GSM -1));
/**
* network type bitmask indicating the support of radio tech GPRS.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_GPRS = (1 << (NETWORK_TYPE_GPRS -1));
/**
* network type bitmask indicating the support of radio tech EDGE.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_EDGE = (1 << (NETWORK_TYPE_EDGE -1));
/**
* network type bitmask indicating the support of radio tech CDMA(IS95A/IS95B).
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_CDMA = (1 << (NETWORK_TYPE_CDMA -1));
/**
* network type bitmask indicating the support of radio tech 1xRTT.
- * @hide
*/
- @SystemApi
+ @SuppressLint("AllUpper")
public static final long NETWORK_TYPE_BITMASK_1xRTT = (1 << (NETWORK_TYPE_1xRTT - 1));
// 3G
/**
* network type bitmask indicating the support of radio tech EVDO 0.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_EVDO_0 = (1 << (NETWORK_TYPE_EVDO_0 -1));
/**
* network type bitmask indicating the support of radio tech EVDO A.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_EVDO_A = (1 << (NETWORK_TYPE_EVDO_A - 1));
/**
* network type bitmask indicating the support of radio tech EVDO B.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_EVDO_B = (1 << (NETWORK_TYPE_EVDO_B -1));
/**
* network type bitmask indicating the support of radio tech EHRPD.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_EHRPD = (1 << (NETWORK_TYPE_EHRPD -1));
/**
* network type bitmask indicating the support of radio tech HSUPA.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_HSUPA = (1 << (NETWORK_TYPE_HSUPA -1));
/**
* network type bitmask indicating the support of radio tech HSDPA.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_HSDPA = (1 << (NETWORK_TYPE_HSDPA -1));
/**
* network type bitmask indicating the support of radio tech HSPA.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
/**
* network type bitmask indicating the support of radio tech HSPAP.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_HSPAP = (1 << (NETWORK_TYPE_HSPAP -1));
/**
* network type bitmask indicating the support of radio tech UMTS.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_UMTS = (1 << (NETWORK_TYPE_UMTS -1));
/**
* network type bitmask indicating the support of radio tech TD_SCDMA.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = (1 << (NETWORK_TYPE_TD_SCDMA -1));
// 4G
/**
* network type bitmask indicating the support of radio tech LTE.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_LTE = (1 << (NETWORK_TYPE_LTE -1));
/**
* network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
/**
* network type bitmask indicating the support of radio tech NR(New Radio) 5G.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_NR = (1 << (NETWORK_TYPE_NR -1));
/**
* network type bitmask indicating the support of radio tech IWLAN.
- * @hide
*/
- @SystemApi
public static final long NETWORK_TYPE_BITMASK_IWLAN = (1 << (NETWORK_TYPE_IWLAN -1));
/** @hide */
@@ -13731,12 +13680,11 @@
/**
* @return Modem supported radio access family bitmask
*
- * <p>Requires permission: {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
+ * <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE or
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
- * @hide
+ *
+ * @throws SecurityException if the caller does not have the required permission
*/
- @SystemApi
- @TestApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @NetworkTypeBitMask long getSupportedRadioAccessFamily() {
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index a166a5d..fa1bae4 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -26,10 +26,10 @@
import android.net.NetworkCapabilities;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.NetCapability;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.NetworkTypeBitMask;
+import android.telephony.data.ApnSetting.ApnType;
import android.telephony.data.ApnSetting.AuthType;
import android.text.TextUtils;
@@ -245,8 +245,7 @@
* @return The supported APN types bitmask.
* @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getApnTypeBitmask()} instead.
*/
- @Deprecated
- public @ApnType int getSupportedApnTypesBitmask() {
+ @Deprecated public @ApnType int getSupportedApnTypesBitmask() {
if (mApnSetting != null) {
return mApnSetting.getApnTypeBitmask();
}
@@ -425,6 +424,12 @@
return ApnSetting.TYPE_MCX;
case NetworkCapabilities.NET_CAPABILITY_IA:
return ApnSetting.TYPE_IA;
+ case NetworkCapabilities.NET_CAPABILITY_BIP:
+ return ApnSetting.TYPE_BIP;
+ case NetworkCapabilities.NET_CAPABILITY_VSIM:
+ return ApnSetting.TYPE_VSIM;
+ case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
+ return ApnSetting.TYPE_ENTERPRISE;
default:
return ApnSetting.TYPE_NONE;
}
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 1ff6ec1..ec73471 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -261,9 +261,10 @@
}
/**
- * Unthrottles the APN on the current transport. There is no matching "APN throttle" method.
- * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within
- * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
+ * Unthrottles the APN on the current transport.
+ * The APN is throttled when {@link IDataService#setupDataCall} fails within
+ * the time specified by {@link DataCallResponse#getRetryDurationMillis} and will remain
+ * throttled until this method is called.
* <p/>
* see: {@link DataCallResponse#getRetryDurationMillis}
*
@@ -284,9 +285,9 @@
/**
* Unthrottles the DataProfile on the current transport.
- * There is no matching "DataProfile throttle" method.
- * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within
- * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
+ * The DataProfile is throttled when {@link IDataService#setupDataCall} fails within
+ * the time specified by {@link DataCallResponse#getRetryDurationMillis} and will remain
+ * throttled until this method is called.
* <p/>
* see: {@link DataCallResponse#getRetryDurationMillis}
*
diff --git a/telephony/java/android/telephony/data/TrafficDescriptor.java b/telephony/java/android/telephony/data/TrafficDescriptor.java
index 2178fc1..66dcf8f 100644
--- a/telephony/java/android/telephony/data/TrafficDescriptor.java
+++ b/telephony/java/android/telephony/data/TrafficDescriptor.java
@@ -21,8 +21,13 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* A traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2. It is used for UE Route Selection
@@ -31,24 +36,215 @@
* not specify the end point to be used for the data call.
*/
public final class TrafficDescriptor implements Parcelable {
+ /**
+ * The OS/App id
+ *
+ * @hide
+ */
+ public static final class OsAppId {
+ /**
+ * OSId for "Android", using UUID version 5 with namespace ISO OSI.
+ * Prepended to the OsAppId in TrafficDescriptor to use for URSP matching.
+ */
+ public static final UUID ANDROID_OS_ID =
+ UUID.fromString("97a498e3-fc92-5c94-8986-0333d06e4e47");
+
+ /**
+ * Allowed app ids.
+ */
+ // The following app ids are the only apps id Android supports. OEMs or vendors are
+ // prohibited to modify/extend the allowed list, especially passing the real package name to
+ // the network.
+ private static final Set<String> ALLOWED_APP_IDS = Set.of(
+ "ENTERPRISE", "PRIORITIZE_LATENCY", "PRIORITIZE_BANDWIDTH", "CBS"
+ );
+
+ /** OS id in UUID format. */
+ private final @NonNull UUID mOsId;
+
+ /**
+ * App id in string format. Note that Android will not allow use specific app id. This must
+ * be a category/capability identifier.
+ */
+ private final @NonNull String mAppId;
+
+ /**
+ * The differentiator when multiple traffic descriptor has the same OS and app id. Must be
+ * greater than 1.
+ */
+ private final int mDifferentiator;
+
+ /**
+ * Constructor
+ *
+ * @param osId OS id in UUID format.
+ * @param appId App id in string format. Note that Android will not allow use specific app
+ * id. This must be a category/capability identifier.
+ */
+ public OsAppId(@NonNull UUID osId, @NonNull String appId) {
+ this(osId, appId, 1);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param osId OS id in UUID format.
+ * @param appId App id in string format. Note that Android will not allow use specific app
+ * id. This must be a category/capability identifier.
+ * @param differentiator The differentiator when multiple traffic descriptor has the same
+ * OS and app id. Must be greater than 0.
+ */
+ public OsAppId(@NonNull UUID osId, @NonNull String appId, int differentiator) {
+ Objects.requireNonNull(osId);
+ Objects.requireNonNull(appId);
+ if (differentiator < 1) {
+ throw new IllegalArgumentException("Invalid differentiator " + differentiator);
+ }
+
+ mOsId = osId;
+ mAppId = appId;
+ mDifferentiator = differentiator;
+ }
+
+ /**
+ * Constructor from raw byte array.
+ *
+ * @param rawOsAppId The raw OS/App id.
+ */
+ public OsAppId(@NonNull byte[] rawOsAppId) {
+ try {
+ ByteBuffer bb = ByteBuffer.wrap(rawOsAppId);
+ // OS id is the first 16 bytes.
+ mOsId = new UUID(bb.getLong(), bb.getLong());
+ // App id length is 1 byte.
+ int appIdLen = bb.get();
+ // The remaining is the app id + differentiator.
+ byte[] appIdAndDifferentiator = new byte[appIdLen];
+ bb.get(appIdAndDifferentiator, 0, appIdLen);
+ // Extract trailing numbers, for example, "ENTERPRISE", "ENTERPRISE3".
+ String appIdAndDifferentiatorStr = new String(appIdAndDifferentiator);
+ Pattern pattern = Pattern.compile("[^0-9]+([0-9]+)$");
+ Matcher matcher = pattern.matcher(new String(appIdAndDifferentiator));
+ if (matcher.find()) {
+ mDifferentiator = Integer.parseInt(matcher.group(1));
+ mAppId = appIdAndDifferentiatorStr.replace(matcher.group(1), "");
+ } else {
+ mDifferentiator = 1;
+ mAppId = appIdAndDifferentiatorStr;
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to decode " + (rawOsAppId != null
+ ? new BigInteger(1, rawOsAppId).toString(16) : null));
+ }
+ }
+
+ /**
+ * @return The OS id in UUID format.
+ */
+ public @NonNull UUID getOsId() {
+ return mOsId;
+ }
+
+ /**
+ * @return App id in string format. Note that Android will not allow use specific app id.
+ * This must be a category/capability identifier.
+ */
+ public @NonNull String getAppId() {
+ return mAppId;
+ }
+
+ /**
+ * @return The differentiator when multiple traffic descriptor has the same OS and app id.
+ * Must be greater than 1.
+ */
+ public int getDifferentiator() {
+ return mDifferentiator;
+ }
+
+ /**
+ * @return OS/App id in raw byte format.
+ */
+ public @NonNull byte[] getBytes() {
+ byte[] osAppId = (mAppId + (mDifferentiator > 1 ? mDifferentiator : "")).getBytes();
+ // 16 bytes for UUID, 1 byte for length of osAppId, and up to 255 bytes for osAppId
+ ByteBuffer bb = ByteBuffer.allocate(16 + 1 + osAppId.length);
+ bb.putLong(mOsId.getMostSignificantBits());
+ bb.putLong(mOsId.getLeastSignificantBits());
+ bb.put((byte) osAppId.length);
+ bb.put(osAppId);
+ return bb.array();
+ }
+
+ @Override
+ public String toString() {
+ return "[OsAppId: OS=" + mOsId + ", App=" + mAppId + ", differentiator="
+ + mDifferentiator + ", raw="
+ + new BigInteger(1, getBytes()).toString(16) + "]";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ OsAppId osAppId = (OsAppId) o;
+ return mDifferentiator == osAppId.mDifferentiator && mOsId.equals(osAppId.mOsId)
+ && mAppId.equals(osAppId.mAppId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mOsId, mAppId, mDifferentiator);
+ }
+ }
+
private final String mDnn;
- private final byte[] mOsAppId;
+ private final OsAppId mOsAppId;
private TrafficDescriptor(@NonNull Parcel in) {
mDnn = in.readString();
- mOsAppId = in.createByteArray();
+ byte[] osAppIdBytes = in.createByteArray();
+ OsAppId osAppId = null;
+ if (osAppIdBytes != null) {
+ osAppId = new OsAppId(osAppIdBytes);
+ }
+ mOsAppId = osAppId;
+
+ enforceAllowedIds();
}
/**
* Create a traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2
* @param dnn optional DNN, which must be used for traffic matching, if present
- * @param osAppId OsId + osAppId of the traffic descriptor
+ * @param osAppIdRawBytes Raw bytes of OsId + osAppId of the traffic descriptor
*
* @hide
*/
- public TrafficDescriptor(String dnn, byte[] osAppId) {
+ public TrafficDescriptor(String dnn, @Nullable byte[] osAppIdRawBytes) {
mDnn = dnn;
+ OsAppId osAppId = null;
+ if (osAppIdRawBytes != null) {
+ osAppId = new OsAppId(osAppIdRawBytes);
+ }
mOsAppId = osAppId;
+
+ enforceAllowedIds();
+ }
+
+ /**
+ * Enforce the OS id and app id are in the allowed list.
+ *
+ * @throws IllegalArgumentException if ids are not allowed.
+ */
+ private void enforceAllowedIds() {
+ if (mOsAppId != null && !mOsAppId.getOsId().equals(OsAppId.ANDROID_OS_ID)) {
+ throw new IllegalArgumentException("OS id " + mOsAppId.getOsId() + " does not match "
+ + OsAppId.ANDROID_OS_ID);
+ }
+
+ if (mOsAppId != null && !OsAppId.ALLOWED_APP_IDS.contains(mOsAppId.getAppId())) {
+ throw new IllegalArgumentException("Illegal app id " + mOsAppId.getAppId()
+ + ". Only allowing one of the following " + OsAppId.ALLOWED_APP_IDS);
+ }
}
/**
@@ -61,13 +257,13 @@
}
/**
- * OsAppId is the app id as defined in 3GPP TS 24.526 Section 5.2, and it identifies a traffic
- * category. It includes the OS Id component of the field as defined in the specs.
- * @return the OS App ID of this traffic descriptor if one is included by the network, null
- * otherwise.
+ * OsAppId identifies a broader traffic category. Although it names Os/App id, it only includes
+ * OS version with a general/broader category id used as app id.
+ *
+ * @return The id in byte format. {@code null} if not available.
*/
public @Nullable byte[] getOsAppId() {
- return mOsAppId;
+ return mOsAppId != null ? mOsAppId.getBytes() : null;
}
@Override
@@ -77,13 +273,13 @@
@NonNull @Override
public String toString() {
- return "TrafficDescriptor={mDnn=" + mDnn + ", mOsAppId=" + mOsAppId + "}";
+ return "TrafficDescriptor={mDnn=" + mDnn + ", " + mOsAppId + "}";
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mDnn);
- dest.writeByteArray(mOsAppId);
+ dest.writeByteArray(mOsAppId != null ? mOsAppId.getBytes() : null);
}
public static final @NonNull Parcelable.Creator<TrafficDescriptor> CREATOR =
@@ -104,7 +300,7 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TrafficDescriptor that = (TrafficDescriptor) o;
- return Objects.equals(mDnn, that.mDnn) && Arrays.equals(mOsAppId, that.mOsAppId);
+ return Objects.equals(mDnn, that.mDnn) && Objects.equals(mOsAppId, that.mOsAppId);
}
@Override
@@ -148,7 +344,7 @@
}
/**
- * Set the OS App ID (including OS Id as defind in the specs).
+ * Set the OS App ID (including OS Id as defined in the specs).
*
* @return The same instance of the builder.
*/
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index c25ace0..f367e40 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -34,7 +34,7 @@
/**@hide*/
@StringDef(prefix = "RCS_PROFILE_",
- value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3})
+ value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4})
public @interface StringRcsProfile {}
/**
@@ -45,6 +45,10 @@
* RCS profile UP 2.3
*/
public static final String RCS_PROFILE_2_3 = "UP_2.3";
+ /**
+ * RCS profile UP 2.4
+ */
+ public static final String RCS_PROFILE_2_4 = "UP_2.4";
private String mRcsVersion;
private String mRcsProfile;
@@ -58,8 +62,8 @@
* @param rcsVersion The parameter identifies the RCS version supported
* by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
* @param rcsProfile Identifies a fixed set of RCS services that are
- * supported by the client. See {@link #RCS_PROFILE_1_0 } or
- * {@link #RCS_PROFILE_2_3 }
+ * supported by the client. See {@link #RCS_PROFILE_1_0 },
+ * {@link #RCS_PROFILE_2_3 } or {@link #RCS_PROFILE_2_4 }
* @param clientVendor Identifies the vendor providing the RCS client.
* @param clientVersion Identifies the RCS client version. Refer to GSMA
* RCC.07 "client_version" parameter.
@@ -80,8 +84,8 @@
* @param rcsVersion The parameter identifies the RCS version supported
* by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
* @param rcsProfile Identifies a fixed set of RCS services that are
- * supported by the client. See {@link #RCS_PROFILE_1_0 } or
- * {@link #RCS_PROFILE_2_3 }
+ * supported by the client. See {@link #RCS_PROFILE_1_0 },
+ * {@link #RCS_PROFILE_2_3 } or {@link #RCS_PROFILE_2_4 }
* @param clientVendor Identifies the vendor providing the RCS client.
* @param clientVersion Identifies the RCS client version. Refer to GSMA
* RCC.07 "client_version" parameter.
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/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 4e98f42..d4fa1dd 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -24,14 +24,21 @@
java_test_host {
name: "ApkVerityTest",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+ libs: [
+ "tradefed",
+ "compatibility-tradefed",
+ "compatibility-host-util",
+ ],
static_libs: [
"block_device_writer_jar",
"frameworks-base-hostutils",
],
- test_suites: ["general-tests", "vts"],
- target_required: [
- "block_device_writer_module",
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ data_device_bins: [
+ "block_device_writer",
],
data: [
":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 0b5f0f6..e5d009d 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,12 +24,7 @@
}
cc_test {
- // Depending on how the test runs, the executable may be uploaded to different location.
- // Before the bug in the file pusher is fixed, workaround by making the name unique.
- // See b/124718249#comment12.
- name: "block_device_writer_module",
- stem: "block_device_writer",
-
+ name: "block_device_writer",
srcs: ["block_device_writer.cpp"],
cflags: [
"-D_FILE_OFFSET_BITS=64",
@@ -38,31 +33,25 @@
"-Wextra",
"-g",
],
- shared_libs: ["libbase", "libutils"],
- // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
- // the uploader does not pick up the executable from correct output location. The following
- // workaround allows the test to:
- // * upload the 32-bit exectuable for both 32 and 64 bits devices to use
- // * refer to the same executable name in Java
- // * no need to force the Java test to be archiecture specific.
- //
- // See b/145573317 for details.
- multilib: {
- lib32: {
- suffix: "",
- },
- lib64: {
- suffix: "64", // not really used
- },
- },
+ shared_libs: [
+ "libbase",
+ "libutils",
+ ],
+ compile_multilib: "first",
auto_gen_config: false,
- test_suites: ["general-tests", "pts", "vts"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
gtest: false,
}
java_library_host {
name: "block_device_writer_jar",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "junit"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 5c2c15b..730daf3 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@
* <p>To use this class, please push block_device_writer binary to /data/local/tmp.
* 1. In Android.bp, add:
* <pre>
- * target_required: ["block_device_writer_module"],
+ * data_device_bins: ["block_device_writer"],
* </pre>
* 2. In AndroidText.xml, add:
* <pre>
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 48bfd6f..6290292 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
@@ -11,10 +11,20 @@
import org.junit.Test
import org.junit.runner.RunWith
import com.google.common.truth.Truth.assertThat
+import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
+import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE
import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED
-import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
+import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN
+import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
import java.lang.IllegalArgumentException
+import java.io.ByteArrayOutputStream
+import java.security.KeyPairGenerator
+import java.security.KeyStore
import java.time.Duration
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -23,25 +33,26 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class SystemAttestationVerificationTest {
-
@get:Rule
val rule = ActivityScenarioRule(TestActivity::class.java)
private lateinit var activity: Activity
private lateinit var avm: AttestationVerificationManager
+ private lateinit var androidKeystore: KeyStore
@Before
fun setup() {
rule.getScenario().onActivity {
avm = it.getSystemService(AttestationVerificationManager::class.java)
activity = it
+ androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
}
}
@Test
fun verifyAttestation_returnsUnknown() {
val future = CompletableFuture<Int>()
- val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+ val profile = AttestationProfile(PROFILE_PEER_DEVICE)
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
activity.mainExecutor) { result, _ ->
future.complete(result)
@@ -51,9 +62,82 @@
}
@Test
- fun verifyToken_returnsUnknown() {
+ fun verifyAttestation_returnsFailureWithEmptyAttestation() {
val future = CompletableFuture<Int>()
val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+ avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0),
+ activity.mainExecutor) { result, _ ->
+ future.complete(result)
+ }
+
+ assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailureWithEmptyRequirements() {
+ val future = CompletableFuture<Int>()
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+ Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+ future.complete(result)
+ }
+ assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailureWithWrongBindingType() {
+ val future = CompletableFuture<Int>()
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ avm.verifyAttestation(selfTrusted.profile, TYPE_PUBLIC_KEY,
+ selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+ future.complete(result)
+ }
+ assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailureWithWrongRequirements() {
+ val future = CompletableFuture<Int>()
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ val wrongKeyRequirements = Bundle()
+ wrongKeyRequirements.putByteArray(
+ "wrongBindingKey", "challengeStr".encodeToByteArray())
+ avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+ wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+ future.complete(result)
+ }
+ assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailureWithWrongChallenge() {
+ val future = CompletableFuture<Int>()
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ val wrongChallengeRequirements = Bundle()
+ wrongChallengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
+ avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+ wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) {
+ result, _ -> future.complete(result)
+ }
+ assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ }
+
+ // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED.
+ @Test
+ fun verifyAttestation_returnsSuccess() {
+ val future = CompletableFuture<Int>()
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+ selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
+ future.complete(result)
+ }
+ assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS)
+ }
+
+ @Test
+ fun verifyToken_returnsUnknown() {
+ val future = CompletableFuture<Int>()
+ val profile = AttestationProfile(PROFILE_PEER_DEVICE)
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
activity.mainExecutor) { _, token ->
val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null)
@@ -66,7 +150,7 @@
@Test
fun verifyToken_tooBigMaxAgeThrows() {
val future = CompletableFuture<VerificationToken>()
- val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+ val profile = AttestationProfile(PROFILE_PEER_DEVICE)
avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
activity.mainExecutor) { _, token ->
future.complete(token)
@@ -87,4 +171,52 @@
super.onCreate(savedInstanceState)
}
}
+
+ inner class TestSelfTrustedAttestation(val alias: String, val challenge: String) {
+ val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
+ val localBindingType = TYPE_CHALLENGE
+ val requirements: Bundle
+ val attestation: ByteArray
+
+ init {
+ val challengeByteArray = challenge.encodeToByteArray()
+ generateAndStoreKey(alias, challengeByteArray)
+ attestation = generateCertificatesByteArray(alias)
+ requirements = Bundle()
+ requirements.putByteArray(PARAM_CHALLENGE, challengeByteArray)
+ }
+
+ private fun generateAndStoreKey(alias: String, challenge: ByteArray) {
+ val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_EC,
+ ANDROID_KEYSTORE
+ )
+ val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
+ ).run {
+ // a challenge results in a generated attestation
+ setAttestationChallenge(challenge)
+ setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+ build()
+ }
+ kpg.initialize(parameterSpec)
+ kpg.generateKeyPair()
+ }
+
+ private fun generateCertificatesByteArray(alias: String): ByteArray {
+ val pkEntry = androidKeystore.getEntry(alias, null) as KeyStore.PrivateKeyEntry
+ val certs = pkEntry.certificateChain
+ val bos = ByteArrayOutputStream()
+ certs.forEach {
+ bos.write(it.encoded)
+ }
+ return bos.toByteArray()
+ }
+ }
+
+ companion object {
+ private const val TAG = "AVFTEST"
+ private const val ANDROID_KEYSTORE = "AndroidKeyStore"
+ }
}
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 566c725..98d13e8 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -26,8 +26,16 @@
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6600s" />
<option name="hidden-api-checks" value="false" />
+ <option name="device-listeners"
+ value="com.android.server.wm.flicker.TraceFileReadyListener" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+ <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+ <option name="collect-on-run-ended-only" value="false" />
+ <option name="clean-up" value="true" />
+ </metrics_collector>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 8fe0029..b66c45c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -19,11 +19,13 @@
import android.app.Instrumentation
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Display
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
@@ -36,6 +38,10 @@
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+
+ private val secondActivityComponent =
+ ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+
fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT)
@@ -47,8 +53,11 @@
button.click()
device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT)
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForFullScreenApp(component)
+ wmHelper.waitForFullScreenApp(secondActivityComponent)
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
+ WindowManagerConditionsFactory.hasLayersAnimating().negate()
+ )
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 648353e..195af58 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -70,7 +70,7 @@
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
setup {
- eachRun {
+ test {
testApp.launchViaIntent(wmHelper)
wmHelper.waitForFullScreenApp(testApp.component)
}
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
+}
diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml
index 2d46217..afdfe79 100644
--- a/tests/componentalias/AndroidTest-template.xml
+++ b/tests/componentalias/AndroidTest-template.xml
@@ -21,8 +21,6 @@
<option name="test-file-name" value="ComponentAliasTests2.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android" />
-
<!-- Exempt the helper APKs from the BG restriction, so they can start BG services. -->
<option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" />
<option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" />
diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
index 89db2f7..9658d6f 100644
--- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
+++ b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
@@ -17,6 +17,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.os.Build;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -27,6 +28,7 @@
import com.android.compatibility.common.util.TestUtils;
import org.junit.AfterClass;
+import org.junit.Assume;
import org.junit.Before;
import java.util.function.Consumer;
@@ -37,7 +39,11 @@
protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
@Before
- public void enableComponentAlias() throws Exception {
+ public void enableComponentAliasWithCompatFlag() throws Exception {
+ Assume.assumeTrue(Build.isDebuggable());
+ ShellUtils.runShellCommand(
+ "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
+ sDeviceConfig.set("enable_experimental_component_alias", "");
sDeviceConfig.set("component_alias_overrides", "");
// Make sure the feature is actually enabled.
@@ -49,6 +55,8 @@
@AfterClass
public static void restoreDeviceConfig() throws Exception {
+ ShellUtils.runShellCommand(
+ "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
sDeviceConfig.close();
}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
new file mode 100644
index 0000000..52c6d5b
--- /dev/null
+++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests;
+
+import android.os.Build;
+import android.provider.DeviceConfig;
+
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.ShellUtils;
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class ComponentAliasEnableWithDeviceConfigTest {
+ protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
+
+ @AfterClass
+ public static void restoreDeviceConfig() throws Exception {
+ sDeviceConfig.close();
+ }
+
+ @Test
+ public void enableComponentAliasWithCompatFlag() throws Exception {
+ Assume.assumeTrue(Build.isDebuggable());
+
+ sDeviceConfig.set("component_alias_overrides", "");
+
+ // First, disable with both compat-id and device config.
+ ShellUtils.runShellCommand(
+ "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
+ sDeviceConfig.set("enable_experimental_component_alias", "");
+
+ TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
+ return ShellUtils.runShellCommand("dumpsys activity component-alias")
+ .indexOf("Enabled: false") > 0;
+ });
+
+ // Then, enable by device config.
+ sDeviceConfig.set("enable_experimental_component_alias", "true");
+
+ // Make sure the feature is actually enabled.
+ TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
+ return ShellUtils.runShellCommand("dumpsys activity component-alias")
+ .indexOf("Enabled: true") > 0;
+ });
+ }
+}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
new file mode 100644
index 0000000..7935476
--- /dev/null
+++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.componentalias.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.provider.DeviceConfig;
+
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.Test;
+
+/**
+ * Test to make sure component-alias can't be enabled on user builds.
+ */
+public class ComponentAliasNotSupportedOnUserBuildTest {
+ protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
+
+ @AfterClass
+ public static void restoreDeviceConfig() throws Exception {
+ sDeviceConfig.close();
+ }
+
+ @Test
+ public void enableComponentAliasWithCompatFlag() throws Exception {
+ Assume.assumeFalse(Build.isDebuggable());
+
+ // Try to enable it by both the device config and compat-id.
+ sDeviceConfig.set("enable_experimental_component_alias", "true");
+ ShellUtils.runShellCommand(
+ "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
+
+ // Sleep for an arbitrary amount of time, so the config would sink in, if there was
+ // no "not on user builds" check.
+
+ Thread.sleep(5000);
+
+ // Make sure the feature is still disabled.
+ assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias")
+ .indexOf("Enabled: false") > 0).isTrue();
+ }
+}
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index 41f73cd..228520e 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -18,6 +18,7 @@
"java/**/*.kt",
],
platform_apis: true,
+ defaults: ["framework-connectivity-test-defaults"],
test_suites: ["device-tests"],
certificate: "platform",
static_libs: [
@@ -28,6 +29,7 @@
"net-tests-utils",
"platform-test-annotations",
"services.core",
+ "service-connectivity-tiramisu-pre-jarjar",
],
libs: [
"android.test.runner",
diff --git a/tests/vcn/AndroidManifest.xml b/tests/vcn/AndroidManifest.xml
index 2ad9aac..a8f657c 100644
--- a/tests/vcn/AndroidManifest.xml
+++ b/tests/vcn/AndroidManifest.xml
@@ -16,7 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.tests.vcn">
-
+ <uses-sdk android:minSdkVersion="33"
+ android:targetSdkVersion="33"/>
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tools/lint/README.md b/tools/lint/README.md
index 2b6d65b..b534b62 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -40,6 +40,9 @@
- If you want to build lint reports for more than 1 module and they include a common module in their
`defaults` field, e.g. `platform_service_defaults`, you can add the `lint` property to that common
module instead of adding it in every module.
+- If you want to run a single lint type, use the `ANDROID_LINT_CHECK`
+ environment variable with the id of the lint. For example:
+ `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
## Create or update a baseline
diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
index bb0cc97..21700d5 100644
--- a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
+++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -42,8 +42,10 @@
private boolean m80211nSupported;
private boolean m80211acSupported;
private boolean m80211axSupported;
+ private boolean m80211beSupported;
private boolean mChannelWidth160MhzSupported;
private boolean mChannelWidth80p80MhzSupported;
+ private boolean mChannelWidth320MhzSupported;
private int mMaxNumberTxSpatialStreams;
private int mMaxNumberRxSpatialStreams;
@@ -53,8 +55,10 @@
m80211nSupported = false;
m80211acSupported = false;
m80211axSupported = false;
+ m80211beSupported = false;
mChannelWidth160MhzSupported = false;
mChannelWidth80p80MhzSupported = false;
+ mChannelWidth320MhzSupported = false;
mMaxNumberTxSpatialStreams = 1;
mMaxNumberRxSpatialStreams = 1;
}
@@ -76,6 +80,8 @@
return m80211acSupported;
case ScanResult.WIFI_STANDARD_11AX:
return m80211axSupported;
+ case ScanResult.WIFI_STANDARD_11BE:
+ return m80211beSupported;
default:
Log.e(TAG, "isWifiStandardSupported called with invalid standard: " + standard);
return false;
@@ -100,6 +106,9 @@
case ScanResult.WIFI_STANDARD_11AX:
m80211axSupported = support;
break;
+ case ScanResult.WIFI_STANDARD_11BE:
+ m80211beSupported = support;
+ break;
default:
Log.e(TAG, "setWifiStandardSupport called with invalid standard: " + standard);
}
@@ -117,13 +126,16 @@
case ScanResult.CHANNEL_WIDTH_20MHZ:
return true;
case ScanResult.CHANNEL_WIDTH_40MHZ:
- return (m80211nSupported || m80211acSupported || m80211axSupported);
+ return (m80211nSupported || m80211acSupported || m80211axSupported
+ || m80211beSupported);
case ScanResult.CHANNEL_WIDTH_80MHZ:
- return (m80211acSupported || m80211axSupported);
+ return (m80211acSupported || m80211axSupported || m80211beSupported);
case ScanResult.CHANNEL_WIDTH_160MHZ:
return mChannelWidth160MhzSupported;
case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
return mChannelWidth80p80MhzSupported;
+ case ScanResult.CHANNEL_WIDTH_320MHZ:
+ return mChannelWidth320MhzSupported;
default:
Log.e(TAG, "isChannelWidthSupported called with invalid channel width: " + chWidth);
}
@@ -133,8 +145,9 @@
/**
* Set support for channel bandwidth
*
- * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and
- * {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ}
+ * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ},
+ * {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ} and
+ * {@link ScanResult#CHANNEL_WIDTH_320MHZ}
* @param support {@code true} if supported, {@code false} otherwise.
*
* @hide
@@ -147,6 +160,9 @@
case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
mChannelWidth80p80MhzSupported = support;
break;
+ case ScanResult.CHANNEL_WIDTH_320MHZ:
+ mChannelWidth320MhzSupported = support;
+ break;
default:
Log.e(TAG, "setChannelWidthSupported called with Invalid channel width: "
+ chWidth);
@@ -205,8 +221,10 @@
return m80211nSupported == capa.m80211nSupported
&& m80211acSupported == capa.m80211acSupported
&& m80211axSupported == capa.m80211axSupported
+ && m80211beSupported == capa.m80211beSupported
&& mChannelWidth160MhzSupported == capa.mChannelWidth160MhzSupported
&& mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
+ && mChannelWidth320MhzSupported == capa.mChannelWidth320MhzSupported
&& mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
&& mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
}
@@ -215,8 +233,9 @@
@Override
public int hashCode() {
return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
- mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
- mMaxNumberTxSpatialStreams, mMaxNumberRxSpatialStreams);
+ m80211beSupported, mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
+ mChannelWidth320MhzSupported, mMaxNumberTxSpatialStreams,
+ mMaxNumberRxSpatialStreams);
}
/** implement Parcelable interface */
@@ -234,8 +253,10 @@
out.writeBoolean(m80211nSupported);
out.writeBoolean(m80211acSupported);
out.writeBoolean(m80211axSupported);
+ out.writeBoolean(m80211beSupported);
out.writeBoolean(mChannelWidth160MhzSupported);
out.writeBoolean(mChannelWidth80p80MhzSupported);
+ out.writeBoolean(mChannelWidth320MhzSupported);
out.writeInt(mMaxNumberTxSpatialStreams);
out.writeInt(mMaxNumberRxSpatialStreams);
}
@@ -246,10 +267,13 @@
sb.append("m80211nSupported:").append(m80211nSupported ? "Yes" : "No");
sb.append("m80211acSupported:").append(m80211acSupported ? "Yes" : "No");
sb.append("m80211axSupported:").append(m80211axSupported ? "Yes" : "No");
+ sb.append("m80211beSupported:").append(m80211beSupported ? "Yes" : "No");
sb.append("mChannelWidth160MhzSupported: ")
.append(mChannelWidth160MhzSupported ? "Yes" : "No");
sb.append("mChannelWidth80p80MhzSupported: ")
.append(mChannelWidth80p80MhzSupported ? "Yes" : "No");
+ sb.append("mChannelWidth320MhzSupported: ")
+ .append(mChannelWidth320MhzSupported ? "Yes" : "No");
sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
@@ -268,8 +292,10 @@
capabilities.m80211nSupported = in.readBoolean();
capabilities.m80211acSupported = in.readBoolean();
capabilities.m80211axSupported = in.readBoolean();
+ capabilities.m80211beSupported = in.readBoolean();
capabilities.mChannelWidth160MhzSupported = in.readBoolean();
capabilities.mChannelWidth80p80MhzSupported = in.readBoolean();
+ capabilities.mChannelWidth320MhzSupported = in.readBoolean();
capabilities.mMaxNumberTxSpatialStreams = in.readInt();
capabilities.mMaxNumberRxSpatialStreams = in.readInt();
return capabilities;
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 459696e..d3eb8e0 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -493,6 +493,8 @@
return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
case IApInterfaceEventCallback.BANDWIDTH_160:
return SoftApInfo.CHANNEL_WIDTH_160MHZ;
+ case IApInterfaceEventCallback.BANDWIDTH_320:
+ return SoftApInfo.CHANNEL_WIDTH_320MHZ;
default:
return SoftApInfo.CHANNEL_WIDTH_INVALID;
}