Merge "Import translations. DO NOT MERGE ANYWHERE" into sc-dev
diff --git a/Android.bp b/Android.bp
index 1c9eac9..0a3ca3b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,8 @@
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
+ ":idmap2_aidl",
+ ":idmap2_core_aidl",
":incidentcompanion_aidl",
":inputconstants_aidl",
":installd_aidl",
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 8723515..8fcd2f9 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -146,19 +146,17 @@
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
- * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
- * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
- * visibility settings apply only to the schemas that are included in the {@code request}.
- * Visibility settings for a schema type do not apply or persist across
- * {@link SetSchemaRequest}s.
+ * <p>By default, documents are visible on platform surfaces. To opt out, call
+ * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
+ * false. Any visibility settings apply only to the schemas that are included in the
+ * {@code request}. Visibility settings for a schema type do not persist across
+ * {@link #setSchema} calls.
*
* @param request The schema update request.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
*/
- // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
- // exposed.
public void setSchema(
@NonNull SetSchemaRequest request,
@NonNull @CallbackExecutor Executor executor,
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 271129b..a45fa39 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -41,6 +41,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -58,8 +59,11 @@
private PackageManagerInternal mPackageManagerInternal;
private ImplInstanceManager mImplInstanceManager;
- // Cache of unlocked user ids so we don't have to query UserManager service each time.
- private final Set<Integer> mUnlockedUserIds = new ArraySet<>();
+ // Cache of unlocked user ids so we don't have to query UserManager service each time. The
+ // "locked" suffix refers to the fact that access to the field should be locked; unrelated to
+ // the unlocked status of user ids.
+ @GuardedBy("mUnlockedUserIdsLocked")
+ private final Set<Integer> mUnlockedUserIdsLocked = new ArraySet<>();
public AppSearchManagerService(Context context) {
super(context);
@@ -74,7 +78,9 @@
@Override
public void onUserUnlocked(@NonNull TargetUser user) {
- mUnlockedUserIds.add(user.getUserIdentifier());
+ synchronized (mUnlockedUserIdsLocked) {
+ mUnlockedUserIdsLocked.add(user.getUserIdentifier());
+ }
}
private class Stub extends IAppSearchManager.Stub {
@@ -108,7 +114,7 @@
schemasPackageAccessibleBundles.entrySet()) {
List<PackageIdentifier> packageIdentifiers =
new ArrayList<>(entry.getValue().size());
- for (int i = 0; i < packageIdentifiers.size(); i++) {
+ for (int i = 0; i < entry.getValue().size(); i++) {
packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
}
schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
@@ -503,9 +509,11 @@
}
private void verifyUserUnlocked(int callingUserId) {
- if (!mUnlockedUserIds.contains(callingUserId)) {
- throw new IllegalStateException(
- "User " + callingUserId + " is locked or not running.");
+ synchronized (mUnlockedUserIdsLocked) {
+ if (!mUnlockedUserIdsLocked.contains(callingUserId)) {
+ throw new IllegalStateException(
+ "User " + callingUserId + " is locked or not running.");
+ }
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 5ea2a02..82319d4 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -29,6 +29,7 @@
import android.util.SparseArray;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import java.io.File;
@@ -43,7 +44,9 @@
private static ImplInstanceManager sImplInstanceManager;
- private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
+ @GuardedBy("mInstancesLocked")
+ private final SparseArray<AppSearchImpl> mInstancesLocked = new SparseArray<>();
+
private final String mGlobalQuerierPackage;
private ImplInstanceManager(@NonNull String globalQuerierPackage) {
@@ -81,19 +84,16 @@
* @return An initialized {@link AppSearchImpl} for this user
*/
@NonNull
- public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
- throws AppSearchException {
- AppSearchImpl instance = mInstances.get(userId);
- if (instance == null) {
- synchronized (ImplInstanceManager.class) {
- instance = mInstances.get(userId);
- if (instance == null) {
- instance = createImpl(context, userId);
- mInstances.put(userId, instance);
- }
+ public AppSearchImpl getAppSearchImpl(
+ @NonNull Context context, @UserIdInt int userId) throws AppSearchException {
+ synchronized (mInstancesLocked) {
+ AppSearchImpl instance = mInstancesLocked.get(userId);
+ if (instance == null) {
+ instance = createImpl(context, userId);
+ mInstancesLocked.put(userId, instance);
}
+ return instance;
}
- return instance;
}
private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 64dc972..babcd25 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -332,10 +332,8 @@
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
for (int i = 0; i < entry.getValue().size(); i++) {
- // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax
- // nested document uri rules gets synced down.
GenericDocument packageAccessibleDocument =
- new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE)
+ new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE)
.setNamespace(NAMESPACE)
.setPropertyString(
PACKAGE_NAME_PROPERTY,
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 8bff720..2f1817e 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -317,7 +317,7 @@
}
Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
- new ArrayMap<>(schemasNotPlatformSurfaceable.size());
+ new ArrayMap<>(schemasPackageAccessible.size());
for (Map.Entry<String, List<PackageIdentifier>> entry :
schemasPackageAccessible.entrySet()) {
prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index eb1623e..6595d8d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -37,8 +37,9 @@
import java.util.concurrent.Executors;
/**
- * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
- * a consistent interface.
+ * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
+ * consistent interface.
+ *
* @hide
*/
public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@@ -47,7 +48,13 @@
@NonNull
public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
- Context context = ApplicationProvider.getApplicationContext();
+ return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
+ }
+
+ /** Only for use when called from a non-instrumented context. */
+ @NonNull
+ public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
+ @NonNull Context context) {
AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
ExecutorService executor = Executors.newCachedThreadPool();
@@ -62,7 +69,6 @@
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
mGlobalSearchSession = Preconditions.checkNotNull(session);
mExecutor = Preconditions.checkNotNull(executor);
-
}
@NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index 8e62c0e..b2ffd5b 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -86,11 +86,11 @@
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
- * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
- * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
- * visibility settings apply only to the schemas that are included in the {@code request}.
- * Visibility settings for a schema type do not apply or persist across {@link
- * SetSchemaRequest}s.
+ * <p>By default, documents are visible on platform surfaces. To opt out, call
+ * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
+ * false. Any visibility settings apply only to the schemas that are included in the
+ * {@code request}. Visibility settings for a schema type do not persist across
+ * {@link #setSchema} calls.
*
* <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
* schema. You can save your documents by setting {@link
@@ -116,8 +116,6 @@
* @see android.app.appsearch.AppSearchSchema.Migrator
* @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
- // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
- // exposed.
@NonNull
ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 82e967a..3cefe65 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -64,7 +64,7 @@
* and which {@link JobServiceContext} to run each job on.
*/
class JobConcurrencyManager {
- private static final String TAG = JobSchedulerService.TAG;
+ private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
private static final boolean DEBUG = JobSchedulerService.DEBUG;
static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
@@ -321,13 +321,14 @@
}
}
+ /** Return {@code true} if the state was updated. */
@GuardedBy("mLock")
- private void refreshSystemStateLocked() {
+ private boolean refreshSystemStateLocked() {
final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
// Only refresh the information every so often.
if (nowUptime < mNextSystemStateRefreshTime) {
- return;
+ return false;
}
final long start = mStatLogger.getTime();
@@ -340,11 +341,14 @@
}
mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
+ return true;
}
@GuardedBy("mLock")
private void updateCounterConfigLocked() {
- refreshSystemStateLocked();
+ if (!refreshSystemStateLocked()) {
+ return;
+ }
final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
@@ -437,9 +441,10 @@
// (sharing the same Uid as nextPending)
int minPriorityForPreemption = Integer.MAX_VALUE;
int selectedContextId = -1;
- int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ int allWorkTypes = getJobWorkTypes(nextPending);
+ int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
- for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+ for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
JobStatus job = contextIdToJobMap[j];
int preferredUid = preferredUidForContext[j];
if (job == null) {
@@ -483,7 +488,7 @@
if (startingJob) {
// Increase the counters when we're going to start a job.
workTypeForContext[selectedContextId] = workType;
- mWorkCountTracker.stageJob(workType);
+ mWorkCountTracker.stageJob(workType, allWorkTypes);
}
}
if (DEBUG) {
@@ -578,8 +583,10 @@
JobStatus highestPriorityJob = null;
int highPriWorkType = workType;
+ int highPriAllWorkTypes = workType;
JobStatus backupJob = null;
int backupWorkType = WORK_TYPE_NONE;
+ int backupAllWorkTypes = WORK_TYPE_NONE;
for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i);
@@ -589,11 +596,12 @@
if (worker.getPreferredUid() != nextPending.getUid()) {
if (backupJob == null) {
- int workAsType =
- mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ int allWorkTypes = getJobWorkTypes(nextPending);
+ int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType != WORK_TYPE_NONE) {
backupJob = nextPending;
backupWorkType = workAsType;
+ backupAllWorkTypes = allWorkTypes;
}
}
continue;
@@ -611,7 +619,8 @@
// reserved slots. We should just run the highest priority job we can find,
// though it would be ideal to use an available WorkType slot instead of
// overloading slots.
- final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ highPriAllWorkTypes = getJobWorkTypes(nextPending);
+ final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
// Just use the preempted job's work type since this new one is technically
// replacing it anyway.
@@ -624,7 +633,7 @@
if (DEBUG) {
Slog.d(TAG, "Running job " + jobStatus + " as preemption");
}
- mWorkCountTracker.stageJob(highPriWorkType);
+ mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
startJobLocked(worker, highestPriorityJob, highPriWorkType);
} else {
if (DEBUG) {
@@ -635,7 +644,7 @@
if (DEBUG) {
Slog.d(TAG, "Running job " + jobStatus + " instead");
}
- mWorkCountTracker.stageJob(backupWorkType);
+ mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
startJobLocked(worker, backupJob, backupWorkType);
}
}
@@ -647,6 +656,7 @@
// find.
JobStatus highestPriorityJob = null;
int highPriWorkType = workType;
+ int highPriAllWorkTypes = workType;
for (int i = 0; i < pendingJobs.size(); i++) {
final JobStatus nextPending = pendingJobs.get(i);
@@ -654,7 +664,8 @@
continue;
}
- final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+ final int allWorkTypes = getJobWorkTypes(nextPending);
+ final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
continue;
}
@@ -663,6 +674,7 @@
< nextPending.lastEvaluatedPriority) {
highestPriorityJob = nextPending;
highPriWorkType = workAsType;
+ highPriAllWorkTypes = allWorkTypes;
}
}
@@ -672,7 +684,7 @@
if (DEBUG) {
Slog.d(TAG, "About to run job: " + jobStatus);
}
- mWorkCountTracker.stageJob(highPriWorkType);
+ mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
startJobLocked(worker, highestPriorityJob, highPriWorkType);
}
}
@@ -1102,26 +1114,58 @@
}
void incrementPendingJobCount(int workTypes) {
- // We don't know which type we'll classify the job as when we run it yet, so make sure
- // we have space in all applicable slots.
- if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
- mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
- }
- if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
- mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + 1);
- }
- if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
- mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
- }
- if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
- mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
+ adjustPendingJobCount(workTypes, true);
+ }
+
+ void decrementPendingJobCount(int workTypes) {
+ if (adjustPendingJobCount(workTypes, false) > 1) {
+ // We don't need to adjust reservations if only one work type was modified
+ // because that work type is the one we're using.
+
+ // 0 is WORK_TYPE_NONE.
+ int workType = 1;
+ int rem = workTypes;
+ while (rem > 0) {
+ if ((rem & 1) != 0) {
+ maybeAdjustReservations(workType);
+ }
+ rem = rem >>> 1;
+ workType = workType << 1;
+ }
}
}
- void stageJob(@WorkType int workType) {
+ /** Returns the number of WorkTypes that were modified. */
+ private int adjustPendingJobCount(int workTypes, boolean add) {
+ final int adj = add ? 1 : -1;
+
+ int numAdj = 0;
+ // We don't know which type we'll classify the job as when we run it yet, so make sure
+ // we have space in all applicable slots.
+ if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+ mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
+ mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+ mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj);
+ numAdj++;
+ }
+ if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+ mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj);
+ numAdj++;
+ }
+
+ return numAdj;
+ }
+
+ void stageJob(@WorkType int workType, int allWorkTypes) {
final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
mNumStartingJobs.put(workType, newNumStartingJobs);
- mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+ decrementPendingJobCount(allWorkTypes);
if (newNumStartingJobs + mNumRunningJobs.get(workType)
> mNumActuallyReservedSlots.get(workType)) {
mNumUnspecializedRemaining--;
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 82ee5d8..96f3bcc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
final JobConcurrencyManager mConcurrencyManager;
- static final int MSG_JOB_EXPIRED = 0;
+ static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
static final int MSG_CHECK_JOB = 1;
static final int MSG_STOP_JOB = 2;
static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
if (DEBUG) {
Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
}
+ JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+ if (newJs != null) {
+ // This job was stopped because the app scheduled a new job with the same job ID.
+ // Check if the new job is ready to run.
+ mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+ }
return;
}
@@ -1748,7 +1754,11 @@
@Override
public void onRunJobNow(JobStatus jobStatus) {
- mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+ if (jobStatus == null) {
+ mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+ } else {
+ mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+ }
}
final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
return;
}
switch (message.what) {
- case MSG_JOB_EXPIRED: {
- JobStatus runNow = (JobStatus) message.obj;
- // runNow can be null, which is a controller's way of indicating that its
- // state is such that all ready jobs should be run immediately.
- if (runNow != null) {
- if (!isCurrentlyActiveLocked(runNow)
- && isReadyToBeExecutedLocked(runNow)) {
- mJobPackageTracker.notePending(runNow);
- addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+ case MSG_CHECK_INDIVIDUAL_JOB: {
+ JobStatus js = (JobStatus) message.obj;
+ if (js != null) {
+ if (isReadyToBeExecutedLocked(js)) {
+ mJobPackageTracker.notePending(js);
+ addOrderedItem(mPendingJobs, js, sPendingJobComparator);
}
} else {
- queueReadyJobsForExecutionLocked();
+ Slog.e(TAG, "Given null job to check individually");
}
} break;
case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
// This method will check and capture all ready jobs, so we don't need to keep any messages
// in the queue.
mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+ mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
// MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
// jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
mHandler.removeMessages(MSG_CHECK_JOB);
- // This method will capture all expired jobs that are ready, so there's no need to keep
- // the _EXPIRED messages in the queue.
- mHandler.removeMessages(MSG_JOB_EXPIRED);
if (DEBUG) {
Slog.d(TAG, "queuing all ready jobs for execution:");
}
@@ -3178,7 +3183,7 @@
TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
pw.println();
job.dump(pw, " ", false, nowElapsed);
- int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
+ int priority = evaluateJobPriorityLocked(job);
pw.print(" Evaluated priority: ");
pw.println(JobInfo.getPriorityString(priority));
@@ -3344,7 +3349,7 @@
job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
- evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
+ evaluateJobPriorityLocked(job));
proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
nowUptime - job.madeActive);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d15bae0..da6f9fe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -20,6 +20,7 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.Nullable;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -326,6 +327,7 @@
/**
* Used externally to query the running job. Will return null if there is no job running.
*/
+ @Nullable
JobStatus getRunningJobLocked() {
return mRunningJob;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
}
}
- taskStatus.changedAuthorities = null;
- taskStatus.changedUris = null;
}
taskStatus.changedAuthorities = null;
taskStatus.changedUris = null;
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 67fa9bb..a2366df 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -8,9 +8,10 @@
method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes();
method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes();
method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
- method public boolean isHdrTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+ method public boolean isFormatSpecified(@NonNull String);
+ method public boolean isHdrTypeSupported(@NonNull String);
method public boolean isSlowMotionSupported();
- method public boolean isVideoMimeTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+ method public boolean isVideoMimeTypeSupported(@NonNull String);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
}
@@ -24,10 +25,6 @@
method @NonNull public android.media.ApplicationMediaCapabilities build();
}
- public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException {
- ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String);
- }
-
public class MediaCommunicationManager {
method @IntRange(from=1) public int getVersion();
}
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index aefeab6..685cf0d 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -22,7 +22,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.AndroidException;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
@@ -79,17 +78,7 @@
public final class ApplicationMediaCapabilities implements Parcelable {
private static final String TAG = "ApplicationMediaCapabilities";
- /**
- * This exception is thrown when a given format is not specified in the media capabilities.
- */
- public static class FormatNotFoundException extends AndroidException {
- public FormatNotFoundException(@NonNull String format) {
- super(format);
- }
- }
-
/** List of supported video codec mime types. */
- // TODO: init it with avc and mpeg4 as application is assuming to support them.
private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
/** List of unsupported video codec mime types. */
@@ -113,39 +102,54 @@
/**
* Query if a video codec format is supported by the application.
+ * <p>
+ * If the application has not specified supporting the format or not, this will return false.
+ * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+ *
* @param videoMime The mime type of the video codec format. Must be the one used in
* {@link MediaFormat#KEY_MIME}.
* @return true if application supports the video codec format, false otherwise.
- * @throws FormatNotFoundException if the application did not specify the codec either in the
- * supported or unsupported formats.
*/
public boolean isVideoMimeTypeSupported(
- @NonNull String videoMime) throws FormatNotFoundException {
- if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
- return false;
- } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
+ @NonNull String videoMime) {
+ if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
return true;
- } else {
- throw new FormatNotFoundException(videoMime);
}
+ return false;
}
/**
* Query if a HDR type is supported by the application.
+ * <p>
+ * If the application has not specified supporting the format or not, this will return false.
+ * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+ *
* @param hdrType The type of the HDR format.
* @return true if application supports the HDR format, false otherwise.
- * @throws FormatNotFoundException if the application did not specify the format either in the
- * supported or unsupported formats.
*/
public boolean isHdrTypeSupported(
- @NonNull @MediaFeature.MediaHdrType String hdrType) throws FormatNotFoundException {
- if (mUnsupportedHdrTypes.contains(hdrType)) {
- return false;
- } else if (mSupportedHdrTypes.contains(hdrType)) {
+ @NonNull @MediaFeature.MediaHdrType String hdrType) {
+ if (mSupportedHdrTypes.contains(hdrType)) {
return true;
- } else {
- throw new FormatNotFoundException(hdrType);
}
+ return false;
+ }
+
+ /**
+ * Query if a format is specified by the application.
+ * <p>
+ * The format could be either the video format or the hdr format.
+ *
+ * @param format The name of the format.
+ * @return true if application specifies the format, false otherwise.
+ */
+ public boolean isFormatSpecified(@NonNull String format) {
+ if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format)
+ || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) {
+ return true;
+
+ }
+ return false;
}
@Override
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index ce7726a..c924d9a 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -1062,14 +1062,8 @@
"Source video format hint must be set!");
}
- boolean supportHevc = false;
- try {
- supportHevc = mClientCaps.isVideoMimeTypeSupported(
- MediaFormat.MIMETYPE_VIDEO_HEVC);
- } catch (ApplicationMediaCapabilities.FormatNotFoundException ex) {
- // Set to false if application did not specify.
- supportHevc = false;
- }
+ boolean supportHevc = mClientCaps.isVideoMimeTypeSupported(
+ MediaFormat.MIMETYPE_VIDEO_HEVC);
if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals(
mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) {
return true;
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 50f4001..3bad889 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -51,13 +51,18 @@
static: {
enabled: false,
},
+ static_libs: [
+ "libidmap2_protos",
+ ],
shared_libs: [
"libandroidfw",
"libbase",
"libcutils",
- "libutils",
- "libziparchive",
"libidmap2_policies",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ "libz",
+ "libziparchive",
],
},
host: {
@@ -68,15 +73,30 @@
"libandroidfw",
"libbase",
"libcutils",
- "libutils",
- "libziparchive",
"libidmap2_policies",
+ "libidmap2_protos",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ "libz",
+ "libziparchive",
],
},
},
}
cc_library {
+ name: "libidmap2_protos",
+ srcs: [
+ "libidmap2/proto/*.proto",
+ ],
+ host_supported: true,
+ proto: {
+ type: "lite",
+ export_proto_headers: true,
+ },
+}
+
+cc_library {
name: "libidmap2_policies",
defaults: [
"idmap2_defaults",
@@ -88,9 +108,6 @@
enabled: true,
},
android: {
- static: {
- enabled: false,
- },
shared_libs: [
"libandroidfw",
],
@@ -119,6 +136,7 @@
srcs: [
"tests/BinaryStreamVisitorTests.cpp",
"tests/CommandLineOptionsTests.cpp",
+ "tests/FabricatedOverlayTests.cpp",
"tests/FileUtilsTests.cpp",
"tests/Idmap2BinaryTests.cpp",
"tests/IdmapTests.cpp",
@@ -130,20 +148,27 @@
"tests/ResourceUtilsTests.cpp",
"tests/ResultTests.cpp",
"tests/XmlParserTests.cpp",
- "tests/ZipFileTests.cpp",
],
- static_libs: ["libgmock"],
+ required: [
+ "idmap2",
+ ],
+ static_libs: [
+ "libgmock",
+ "libidmap2_protos",
+ ],
target: {
android: {
shared_libs: [
"libandroidfw",
"libbase",
"libidmap2",
+ "libidmap2_policies",
"liblog",
+ "libprotobuf-cpp-lite",
"libutils",
"libz",
+ "libz",
"libziparchive",
- "libidmap2_policies",
],
},
host: {
@@ -152,10 +177,11 @@
"libbase",
"libcutils",
"libidmap2",
+ "libidmap2_policies",
"liblog",
+ "libprotobuf-cpp-lite",
"libutils",
"libziparchive",
- "libidmap2_policies",
],
shared_libs: [
"libz",
@@ -189,6 +215,9 @@
"idmap2/Lookup.cpp",
"idmap2/Main.cpp",
],
+ static_libs: [
+ "libidmap2_protos",
+ ],
target: {
android: {
shared_libs: [
@@ -196,9 +225,11 @@
"libbase",
"libcutils",
"libidmap2",
- "libutils",
- "libziparchive",
"libidmap2_policies",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ "libz",
+ "libziparchive",
],
},
host: {
@@ -207,10 +238,11 @@
"libbase",
"libcutils",
"libidmap2",
+ "libidmap2_policies",
"liblog",
+ "libprotobuf-cpp-lite",
"libutils",
"libziparchive",
- "libidmap2_policies",
],
shared_libs: [
"libz",
@@ -236,11 +268,14 @@
"libbinder",
"libcutils",
"libidmap2",
+ "libidmap2_policies",
+ "libprotobuf-cpp-lite",
"libutils",
"libziparchive",
- "libidmap2_policies",
],
static_libs: [
+ "libc++fs",
+ "libidmap2_protos",
"libidmap2daidl",
],
init_rc: ["idmap2d/idmap2d.rc"],
@@ -248,28 +283,41 @@
cc_library_static {
name: "libidmap2daidl",
- defaults: [
- "idmap2_defaults",
- ],
- tidy: false,
- host_supported: false,
srcs: [
":idmap2_aidl",
+ ":idmap2_core_aidl",
+ ],
+ header_libs: [
+ "libbinder_headers",
],
shared_libs: [
"libbase",
],
aidl: {
export_aidl_headers: true,
+ local_include_dirs: [
+ "idmap2d/aidl/core",
+ "idmap2d/aidl/services/",
+ ],
},
}
filegroup {
+ name: "idmap2_core_aidl",
+ srcs: [
+ "idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl",
+ "idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl",
+ "idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl",
+ ],
+ path: "idmap2d/aidl/core/",
+}
+
+filegroup {
name: "idmap2_aidl",
srcs: [
- "idmap2d/aidl/android/os/IIdmap2.aidl",
+ "idmap2d/aidl/services/android/os/IIdmap2.aidl",
],
- path: "idmap2d/aidl",
+ path: "idmap2d/aidl/services/",
}
aidl_interface {
@@ -281,7 +329,7 @@
filegroup {
name: "overlayable_policy_aidl_files",
srcs: [
- "idmap2d/aidl/android/os/OverlayablePolicy.aidl",
+ "idmap2d/aidl/services/android/os/OverlayablePolicy.aidl",
],
- path: "idmap2d/aidl",
+ path: "idmap2d/aidl/services/",
}
diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
index 09867f3..bf30a76 100644
--- a/cmds/idmap2/idmap2/CommandUtils.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -25,7 +25,9 @@
using android::idmap2::Error;
using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
@@ -39,11 +41,20 @@
return Error("failed to parse idmap header");
}
- const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name,
- fulfilled_policies, enforce_overlayable);
+ auto target = TargetResourceContainer::FromPath(target_path);
+ if (!target) {
+ return Error("failed to load target '%s'", target_path.c_str());
+ }
+
+ auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+ if (!overlay) {
+ return Error("failed to load overlay '%s'", overlay_path.c_str());
+ }
+
+ const auto header_ok = header->IsUpToDate(**target, **overlay, overlay_name, fulfilled_policies,
+ enforce_overlayable);
if (!header_ok) {
return Error(header_ok.GetError(), "idmap not up to date");
}
-
return Unit{};
}
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c93c717..977a0bb 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -20,7 +20,6 @@
#include <fstream>
#include <memory>
#include <ostream>
-#include <string>
#include <vector>
#include "androidfw/ResourceTypes.h"
@@ -31,12 +30,13 @@
#include "idmap2/PolicyUtils.h"
#include "idmap2/SysTrace.h"
-using android::ApkAssets;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
using android::idmap2::utils::kIdmapFilePermissionMask;
using android::idmap2::utils::PoliciesToBitmaskResult;
@@ -93,18 +93,18 @@
fulfilled_policies |= PolicyFlags::PUBLIC;
}
- const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- if (!target_apk) {
- return Error("failed to load apk %s", target_apk_path.c_str());
+ const auto target = TargetResourceContainer::FromPath(target_apk_path);
+ if (!target) {
+ return Error("failed to load target '%s'", target_apk_path.c_str());
}
- const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- if (!overlay_apk) {
- return Error("failed to load apk %s", overlay_apk_path.c_str());
+ const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ if (!overlay) {
+ return Error("failed to load apk overlay '%s'", overlay_apk_path.c_str());
}
- const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name,
- fulfilled_policies, !ignore_overlayable);
+ const auto idmap = Idmap::FromContainers(**target, **overlay, overlay_name, fulfilled_policies,
+ !ignore_overlayable);
if (!idmap) {
return Error(idmap.GetError(), "failed to create idmap");
}
@@ -112,13 +112,14 @@
umask(kIdmapFilePermissionMask);
std::ofstream fout(idmap_path);
if (fout.fail()) {
- return Error("failed to open idmap path %s", idmap_path.c_str());
+ return Error("failed to open idmap path '%s'", idmap_path.c_str());
}
+
BinaryStreamVisitor visitor(fout);
(*idmap)->accept(&visitor);
fout.close();
if (fout.fail()) {
- return Error("failed to write to idmap path %s", idmap_path.c_str());
+ return Error("failed to write to idmap path '%s'", idmap_path.c_str());
}
return Unit{};
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 5db391c..953d99f 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -34,13 +34,14 @@
#include "idmap2/PolicyUtils.h"
#include "idmap2/SysTrace.h"
-using android::ApkAssets;
using android::base::StringPrintf;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
using android::idmap2::Unit;
using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
@@ -91,9 +92,9 @@
fulfilled_policies |= PolicyFlags::PUBLIC;
}
- const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- if (!target_apk) {
- return Error("failed to load apk %s", target_apk_path.c_str());
+ const auto target = TargetResourceContainer::FromPath(target_apk_path);
+ if (!target) {
+ return Error("failed to load target '%s'", target_apk_path.c_str());
}
std::vector<std::string> idmap_paths;
@@ -108,14 +109,14 @@
// TODO(b/175014391): Support multiple overlay tags in OverlayConfig
if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies,
!ignore_overlayable)) {
- const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- if (!overlay_apk) {
+ const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ if (!overlay) {
LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
continue;
}
- const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies,
- !ignore_overlayable);
+ const auto idmap =
+ Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable);
if (!idmap) {
LOG(WARNING) << "failed to create idmap";
continue;
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index 43a1951..f41e57c 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -37,7 +37,6 @@
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
#include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
#include "utils/String16.h"
#include "utils/String8.h"
@@ -52,10 +51,10 @@
using android::idmap2::CommandLineOptions;
using android::idmap2::Error;
using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
using android::idmap2::ResourceId;
using android::idmap2::Result;
using android::idmap2::Unit;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
namespace {
@@ -195,12 +194,17 @@
}
apk_assets.push_back(std::move(target_apk));
- auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(),
- idmap_header->GetOverlayName());
+ auto overlay = OverlayResourceContainer::FromPath(idmap_header->GetOverlayPath());
+ if (!overlay) {
+ return overlay.GetError();
+ }
+
+ auto manifest_info = (*overlay)->FindOverlayInfo(idmap_header->GetOverlayName());
if (!manifest_info) {
return manifest_info.GetError();
}
- target_package_name = manifest_info->target_package;
+
+ target_package_name = (*manifest_info).target_package;
} else if (target_path != idmap_header->GetTargetPath()) {
return Error("different target APKs (expected target APK %s but %s has target APK %s)",
target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str());
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 93537d3..05336ba 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -18,10 +18,10 @@
#include <sys/stat.h> // umask
#include <sys/types.h> // umask
-#include <unistd.h>
#include <cerrno>
#include <cstring>
+#include <filesystem>
#include <fstream>
#include <memory>
#include <ostream>
@@ -35,19 +35,20 @@
#include "idmap2/Idmap.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String8.h"
using android::IPCThreadState;
using android::base::StringPrintf;
using android::binder::Status;
using android::idmap2::BinaryStreamVisitor;
-using android::idmap2::GetPackageCrc;
+using android::idmap2::FabricatedOverlay;
+using android::idmap2::FabricatedOverlayContainer;
using android::idmap2::Idmap;
using android::idmap2::IdmapHeader;
-using android::idmap2::ZipFile;
+using android::idmap2::OverlayResourceContainer;
+using android::idmap2::TargetResourceContainer;
using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::RandomStringForPath;
using android::idmap2::utils::UidHasWriteAccessToPath;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
@@ -69,39 +70,24 @@
return static_cast<PolicyBitmask>(arg);
}
-Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
- const auto zip = ZipFile::Open(apk_path);
- if (!zip) {
- return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
- }
-
- const auto crc = GetPackageCrc(*zip);
- if (!crc) {
- return error(crc.GetErrorMessage());
- }
-
- *out_crc = *crc;
- return ok();
-}
-
} // namespace
namespace android::os {
-Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
+Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
assert(_aidl_return);
- SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_apk_path;
- *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_path;
+ *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
return ok();
}
-Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
- int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+Status Idmap2Service::removeIdmap(const std::string& overlay_path, int32_t user_id ATTRIBUTE_UNUSED,
+ bool* _aidl_return) {
assert(_aidl_return);
- SYSTRACE << "Idmap2Service::removeIdmap " << overlay_apk_path;
+ SYSTRACE << "Idmap2Service::removeIdmap " << overlay_path;
const uid_t uid = IPCThreadState::self()->getCallingUid();
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
if (!UidHasWriteAccessToPath(uid, idmap_path)) {
*_aidl_return = false;
return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
@@ -115,93 +101,88 @@
return ok();
}
-Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
- const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+ const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
bool* _aidl_return) {
- SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
+ SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path;
assert(_aidl_return);
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
std::ifstream fin(idmap_path);
const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
fin.close();
if (!header) {
*_aidl_return = false;
- return error("failed to parse idmap header");
+ LOG(WARNING) << "failed to parse idmap header of '" << idmap_path << "'";
+ return ok();
}
- uint32_t target_crc;
- if (target_apk_path == kFrameworkPath && android_crc_) {
- target_crc = *android_crc_;
- } else {
- auto target_crc_status = GetCrc(target_apk_path, &target_crc);
- if (!target_crc_status.isOk()) {
- *_aidl_return = false;
- return target_crc_status;
- }
-
- // Loading the framework zip can take several milliseconds. Cache the crc of the framework
- // resource APK to reduce repeated work during boot.
- if (target_apk_path == kFrameworkPath) {
- android_crc_ = target_crc;
- }
- }
-
- uint32_t overlay_crc;
- auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
- if (!overlay_crc_status.isOk()) {
+ const auto target = GetTargetContainer(target_path);
+ if (!target) {
*_aidl_return = false;
- return overlay_crc_status;
+ LOG(WARNING) << "failed to load target '" << target_path << "'";
+ return ok();
}
- // TODO(162841629): Support passing overlay name to idmap2d verify
+ const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+ if (!overlay) {
+ *_aidl_return = false;
+ LOG(WARNING) << "failed to load overlay '" << overlay_path << "'";
+ return ok();
+ }
+
auto up_to_date =
- header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc,
+ header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name,
ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
*_aidl_return = static_cast<bool>(up_to_date);
- return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
+ if (!up_to_date) {
+ LOG(WARNING) << "idmap '" << idmap_path
+ << "' not up to date : " << up_to_date.GetErrorMessage();
+ }
+ return ok();
}
-Status Idmap2Service::createIdmap(const std::string& target_apk_path,
- const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::createIdmap(const std::string& target_path, const std::string& overlay_path,
+ const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
std::optional<std::string>* _aidl_return) {
assert(_aidl_return);
- SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
+ SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path;
_aidl_return->reset();
const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
const uid_t uid = IPCThreadState::self()->getCallingUid();
if (!UidHasWriteAccessToPath(uid, idmap_path)) {
return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
idmap_path.c_str(), uid));
}
- const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- if (!target_apk) {
- return error("failed to load apk " + target_apk_path);
+ // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+ // that existing memory maps will continue to be valid and unaffected. The file must be deleted
+ // before attempting to create the idmap, so that if idmap creation fails, the overlay will no
+ // longer be usable.
+ unlink(idmap_path.c_str());
+
+ const auto target = GetTargetContainer(target_path);
+ if (!target) {
+ return error("failed to load target '%s'" + target_path);
}
- const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- if (!overlay_apk) {
- return error("failed to load apk " + overlay_apk_path);
+ const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+ if (!overlay) {
+ return error("failed to load apk overlay '%s'" + overlay_path);
}
- // TODO(162841629): Support passing overlay name to idmap2d create
- const auto idmap =
- Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable);
+ const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name,
+ policy_bitmask, enforce_overlayable);
if (!idmap) {
return error(idmap.GetErrorMessage());
}
- // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
- // that existing memory maps will continue to be valid and unaffected.
- unlink(idmap_path.c_str());
-
umask(kIdmapFilePermissionMask);
std::ofstream fout(idmap_path);
if (fout.fail()) {
@@ -220,4 +201,155 @@
return ok();
}
+idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
+ const std::string& target_path) {
+ if (target_path == kFrameworkPath) {
+ if (framework_apk_cache_ == nullptr) {
+ // Initialize the framework APK cache.
+ auto target = TargetResourceContainer::FromPath(target_path);
+ if (!target) {
+ return target.GetError();
+ }
+ framework_apk_cache_ = std::move(*target);
+ }
+ return {framework_apk_cache_.get()};
+ }
+
+ auto target = TargetResourceContainer::FromPath(target_path);
+ if (!target) {
+ return target.GetError();
+ }
+ return {std::move(*target)};
+}
+
+Status Idmap2Service::createFabricatedOverlay(
+ const os::FabricatedOverlayInternal& overlay,
+ std::optional<os::FabricatedOverlayInfo>* _aidl_return) {
+ idmap2::FabricatedOverlay::Builder builder(overlay.packageName, overlay.overlayName,
+ overlay.targetPackageName);
+ if (!overlay.targetOverlayable.empty()) {
+ builder.SetOverlayable(overlay.targetOverlayable);
+ }
+
+ for (const auto& res : overlay.entries) {
+ builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+ }
+
+ // Generate the file path of the fabricated overlay and ensure it does not collide with an
+ // existing path. Re-registering a fabricated overlay will always result in an updated path.
+ std::string path;
+ std::string file_name;
+ do {
+ constexpr size_t kSuffixLength = 4;
+ const std::string random_suffix = RandomStringForPath(kSuffixLength);
+ file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
+ overlay.overlayName.c_str(), random_suffix.c_str());
+ path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
+
+ // Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
+ // process to abort since the name exceeds the maximum file name size.
+ const size_t kMaxFileNameLength = 255;
+ if (file_name.size() > kMaxFileNameLength) {
+ return error(
+ base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters",
+ file_name.c_str(), kMaxFileNameLength));
+ }
+ } while (std::filesystem::exists(path));
+
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (!UidHasWriteAccessToPath(uid, path)) {
+ return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access",
+ path.c_str(), uid));
+ }
+
+ // Persist the fabricated overlay.
+ umask(kIdmapFilePermissionMask);
+ std::ofstream fout(path);
+ if (fout.fail()) {
+ return error("failed to open frro path " + path);
+ }
+ const auto frro = builder.Build();
+ if (!frro) {
+ return error(StringPrintf("failed to serialize '%s:%s': %s", overlay.packageName.c_str(),
+ overlay.overlayName.c_str(), frro.GetErrorMessage().c_str()));
+ }
+ auto result = frro->ToBinaryStream(fout);
+ if (!result) {
+ unlink(path.c_str());
+ return error("failed to write to frro path " + path + ": " + result.GetErrorMessage());
+ }
+ if (fout.fail()) {
+ unlink(path.c_str());
+ return error("failed to write to frro path " + path);
+ }
+
+ os::FabricatedOverlayInfo out_info;
+ out_info.packageName = overlay.packageName;
+ out_info.overlayName = overlay.overlayName;
+ out_info.targetPackageName = overlay.targetPackageName;
+ out_info.targetOverlayable = overlay.targetOverlayable;
+ out_info.path = path;
+ *_aidl_return = out_info;
+ return ok();
+}
+
+Status Idmap2Service::getFabricatedOverlayInfos(
+ std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+ for (const auto& entry : std::filesystem::directory_iterator(kIdmapCacheDir)) {
+ if (!android::IsFabricatedOverlay(entry.path())) {
+ continue;
+ }
+
+ const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+ if (!overlay) {
+ // This is a sign something went wrong.
+ LOG(ERROR) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
+ continue;
+ }
+
+ const auto info = (*overlay)->GetManifestInfo();
+ os::FabricatedOverlayInfo out_info;
+ out_info.packageName = info.package_name;
+ out_info.overlayName = info.name;
+ out_info.targetPackageName = info.target_package;
+ out_info.targetOverlayable = info.target_name;
+ out_info.path = entry.path();
+ _aidl_return->emplace_back(std::move(out_info));
+ }
+
+ return ok();
+}
+
+binder::Status Idmap2Service::deleteFabricatedOverlay(const std::string& overlay_path,
+ bool* _aidl_return) {
+ SYSTRACE << "Idmap2Service::deleteFabricatedOverlay " << overlay_path;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+
+ if (!UidHasWriteAccessToPath(uid, overlay_path)) {
+ *_aidl_return = false;
+ return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+ overlay_path.c_str(), uid));
+ }
+
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ *_aidl_return = false;
+ return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+ idmap_path.c_str(), uid));
+ }
+
+ if (unlink(overlay_path.c_str()) != 0) {
+ *_aidl_return = false;
+ return error("failed to unlink " + overlay_path + ": " + strerror(errno));
+ }
+
+ if (unlink(idmap_path.c_str()) != 0) {
+ *_aidl_return = false;
+ return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+ }
+
+ *_aidl_return = true;
+ return ok();
+}
+
} // namespace android::os
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 0127e87..4d16ff3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -18,12 +18,14 @@
#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
#include <android-base/unique_fd.h>
+#include <android/os/BnIdmap2.h>
+#include <android/os/FabricatedOverlayInfo.h>
#include <binder/BinderService.h>
+#include <idmap2/ResourceContainer.h>
+#include <idmap2/Result.h>
#include <string>
-#include "android/os/BnIdmap2.h"
-
namespace android::os {
class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
@@ -32,27 +34,58 @@
return "idmap";
}
- binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
+ binder::Status getIdmapPath(const std::string& overlay_path, int32_t user_id,
std::string* _aidl_return) override;
- binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
+ binder::Status removeIdmap(const std::string& overlay_path, int32_t user_id,
bool* _aidl_return) override;
- binder::Status verifyIdmap(const std::string& target_apk_path,
- const std::string& overlay_apk_path, int32_t fulfilled_policies,
+ binder::Status verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+ const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id,
bool* _aidl_return) override;
- binder::Status createIdmap(const std::string& target_apk_path,
- const std::string& overlay_apk_path, int32_t fulfilled_policies,
+ binder::Status createIdmap(const std::string& target_path, const std::string& overlay_path,
+ const std::string& overlay_name, int32_t fulfilled_policies,
bool enforce_overlayable, int32_t user_id,
std::optional<std::string>* _aidl_return) override;
+ binder::Status createFabricatedOverlay(
+ const os::FabricatedOverlayInternal& overlay,
+ std::optional<os::FabricatedOverlayInfo>* _aidl_return) override;
+
+ binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
+ bool* _aidl_return) override;
+
+ binder::Status getFabricatedOverlayInfos(
+ std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
+
private:
- // Cache the crc of the android framework package since the crc cannot change without a reboot.
- std::optional<uint32_t> android_crc_;
+ // idmap2d is killed after a period of inactivity, so any information stored on this class should
+ // be able to be recalculated if idmap2 dies and restarts.
+ std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+
+ std::vector<os::FabricatedOverlayInfo> fabricated_overlays_;
+
+ template <typename T>
+ using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
+
+ using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
+ idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
+
+ template <typename T>
+ WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
};
+template <typename T>
+const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
+ auto u = std::get_if<T*>(&ptr);
+ if (u != nullptr) {
+ return *u;
+ }
+ return std::get<std::unique_ptr<T>>(ptr).get();
+}
+
} // namespace android::os
#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
similarity index 70%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
index 14d57bf..6375d24 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
@@ -14,6 +14,15 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.os;
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInfo {
+ @utf8InCpp String path;
+ @utf8InCpp String packageName;
+ @utf8InCpp String overlayName;
+ @utf8InCpp String targetPackageName;
+ @utf8InCpp String targetOverlayable;
+}
\ No newline at end of file
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
similarity index 64%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
index 14d57bf..f67d8be 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.os;
-parcelable ExternalTimeSuggestion;
+import android.os.FabricatedOverlayInternalEntry;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternal {
+ @utf8InCpp String packageName;
+ @utf8InCpp String overlayName;
+ @utf8InCpp String targetPackageName;
+ @utf8InCpp String targetOverlayable;
+ List<FabricatedOverlayInternalEntry> entries;
+}
\ No newline at end of file
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
similarity index 79%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 14d57bf..6c2af27 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.os;
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternalEntry {
+ @utf8InCpp String resourceName;
+ int dataType;
+ int data;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
similarity index 73%
rename from cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 156f1d7..35bca98 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -16,6 +16,9 @@
package android.os;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInfo;
+
/**
* @hide
*/
@@ -23,13 +26,18 @@
@utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
boolean verifyIdmap(@utf8InCpp String targetApkPath,
- @utf8InCpp String overlayApkPath,
+ @utf8InCpp String overlayApkPath,
+ @utf8InCpp String overlayName,
int fulfilledPolicies,
boolean enforceOverlayable,
int userId);
@nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
@utf8InCpp String overlayApkPath,
+ @utf8InCpp String overlayName,
int fulfilledPolicies,
boolean enforceOverlayable,
int userId);
+ @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
+ List<FabricatedOverlayInfo> getFabricatedOverlayInfos();
+ boolean deleteFabricatedOverlay(@utf8InCpp String path);
}
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
similarity index 100%
rename from cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
new file mode 100644
index 0000000..be687d9
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+#define IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+
+#include <libidmap2/proto/fabricated_v1.pb.h>
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "idmap2/ResourceContainer.h"
+#include "idmap2/Result.h"
+
+namespace android::idmap2 {
+
+struct FabricatedOverlay {
+ struct Builder {
+ Builder(const std::string& package_name, const std::string& name,
+ const std::string& target_package_name);
+
+ Builder& SetOverlayable(const std::string& name);
+
+ Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
+ uint32_t data_value);
+
+ WARN_UNUSED Result<FabricatedOverlay> Build();
+
+ private:
+ struct Entry {
+ std::string resource_name;
+ DataType data_type;
+ DataValue data_value;
+ };
+
+ std::string package_name_;
+ std::string name_;
+ std::string target_package_name_;
+ std::string target_overlayable_;
+ std::vector<Entry> entries_;
+ };
+
+ Result<Unit> ToBinaryStream(std::ostream& stream) const;
+ static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
+
+ private:
+ struct SerializedData {
+ std::unique_ptr<uint8_t[]> data;
+ size_t data_size;
+ uint32_t crc;
+ };
+
+ Result<SerializedData*> InitializeData() const;
+ Result<uint32_t> GetCrc() const;
+
+ FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::optional<uint32_t> crc_from_disk = {});
+
+ pb::FabricatedOverlay overlay_pb_;
+ std::optional<uint32_t> crc_from_disk_;
+ mutable std::optional<SerializedData> data_;
+
+ friend struct FabricatedOverlayContainer;
+};
+
+struct FabricatedOverlayContainer : public OverlayResourceContainer {
+ static Result<std::unique_ptr<FabricatedOverlayContainer>> FromPath(std::string path);
+ static std::unique_ptr<FabricatedOverlayContainer> FromOverlay(FabricatedOverlay&& overlay);
+
+ WARN_UNUSED OverlayManifestInfo GetManifestInfo() const;
+
+ // inherited from OverlayResourceContainer
+ WARN_UNUSED Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+ WARN_UNUSED Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+
+ // inherited from ResourceContainer
+ WARN_UNUSED Result<uint32_t> GetCrc() const override;
+ WARN_UNUSED const std::string& GetPath() const override;
+ WARN_UNUSED Result<std::string> GetResourceName(ResourceId id) const override;
+
+ ~FabricatedOverlayContainer() override;
+
+ private:
+ FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path);
+ FabricatedOverlay overlay_;
+ std::string path_;
+};
+
+} // namespace android::idmap2
+
+#endif // IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index c4e0e1f..2588688 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,7 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+#include <random>
#include <string>
namespace android::idmap2::utils {
@@ -26,6 +27,8 @@
bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
+std::string RandomStringForPath(size_t length);
+
} // namespace android::idmap2::utils
#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 1b815c1..58aff42 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -23,8 +23,8 @@
* debug_info
* data := data_header target_entry* target_inline_entry* overlay_entry*
* string_pool
- * data_header := target_package_id overlay_package_id padding(2) target_entry_count
- * target_inline_entry_count overlay_entry_count string_pool_index
+ * data_header := target_entry_count target_inline_entry_count overlay_entry_count
+ * string_pool_index
* target_entry := target_id overlay_id
* target_inline_entry := target_id Res_value::size padding(1) Res_value::type
* Res_value::value
@@ -68,11 +68,10 @@
#include <vector>
#include "android-base/macros.h"
-#include "androidfw/ApkAssets.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
+#include "idmap2/ResourceContainer.h"
#include "idmap2/ResourceMapping.h"
-#include "idmap2/ZipFile.h"
namespace android::idmap2 {
@@ -85,9 +84,6 @@
// current version of the idmap binary format; must be incremented when the format is changed
static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
-// Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
-Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
-
class IdmapHeader {
public:
static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
@@ -135,9 +131,9 @@
// Invariant: anytime the idmap data encoding is changed, the idmap version
// field *must* be incremented. Because of this, we know that if the idmap
// header is up-to-date the entire file is up-to-date.
- Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
- const std::string& overlay_name, PolicyBitmask fulfilled_policies,
- bool enforce_overlayable) const;
+ Result<Unit> IsUpToDate(const TargetResourceContainer& target,
+ const OverlayResourceContainer& overlay, const std::string& overlay_name,
+ PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
const std::string& overlay_name, uint32_t target_crc,
@@ -169,14 +165,6 @@
public:
static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
- inline PackageId GetTargetPackageId() const {
- return target_package_id_;
- }
-
- inline PackageId GetOverlayPackageId() const {
- return overlay_package_id_;
- }
-
inline uint32_t GetTargetEntryCount() const {
return target_entry_count;
}
@@ -196,8 +184,6 @@
void accept(Visitor* v) const;
private:
- PackageId target_package_id_;
- PackageId overlay_package_id_;
uint32_t target_entry_count;
uint32_t target_entry_inline_count;
uint32_t overlay_entry_count;
@@ -275,11 +261,10 @@
// file is used; change this in the next version of idmap to use a named
// package instead; also update FromApkAssets to take additional parameters:
// the target and overlay package names
- static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
- const ApkAssets& overlay_apk_assets,
- const std::string& overlay_name,
- const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable);
+ static Result<std::unique_ptr<const Idmap>> FromContainers(
+ const TargetResourceContainer& target, const OverlayResourceContainer& overlay,
+ const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable);
const std::unique_ptr<const IdmapHeader>& GetHeader() const {
return header_;
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 2b4c761..4464201 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -41,9 +41,8 @@
private:
std::ostream& stream_;
- AssetManager2 target_am_;
- AssetManager2 overlay_am_;
- std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
+ std::unique_ptr<TargetResourceContainer> target_;
+ std::unique_ptr<OverlayResourceContainer> overlay_;
};
} // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 4583516..ebd0d1e 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -49,10 +49,9 @@
void pad(size_t padding);
std::ostream& stream_;
- std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
- AssetManager2 target_am_;
- AssetManager2 overlay_am_;
size_t offset_;
+ std::unique_ptr<TargetResourceContainer> target_;
+ std::unique_ptr<OverlayResourceContainer> overlay_;
};
} // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
new file mode 100644
index 0000000..74a6f56
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "idmap2/Policies.h"
+#include "idmap2/ResourceUtils.h"
+
+namespace android::idmap2 {
+
+struct ResourceContainer {
+ WARN_UNUSED virtual Result<uint32_t> GetCrc() const = 0;
+ WARN_UNUSED virtual const std::string& GetPath() const = 0;
+ WARN_UNUSED virtual Result<std::string> GetResourceName(ResourceId id) const = 0;
+
+ virtual ~ResourceContainer() = default;
+};
+
+struct TargetResourceContainer : public ResourceContainer {
+ static Result<std::unique_ptr<TargetResourceContainer>> FromPath(std::string path);
+
+ WARN_UNUSED virtual Result<bool> DefinesOverlayable() const = 0;
+ WARN_UNUSED virtual Result<const android::OverlayableInfo*> GetOverlayableInfo(
+ ResourceId id) const = 0;
+ WARN_UNUSED virtual Result<ResourceId> GetResourceId(const std::string& name) const = 0;
+
+ ~TargetResourceContainer() override = default;
+};
+
+struct OverlayManifestInfo {
+ std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
+ std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
+ ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+struct OverlayData {
+ struct ResourceIdValue {
+ // The overlay resource id.
+ ResourceId overlay_id;
+
+ // Whether or not references to the overlay resource id should be rewritten to its corresponding
+ // target id during resource resolution.
+ bool rewrite_id;
+ };
+
+ struct Value {
+ std::string resource_name;
+ std::variant<ResourceIdValue, TargetValue> value;
+ };
+
+ struct InlineStringPoolData {
+ // The binary data of the android::ResStringPool string pool.
+ std::unique_ptr<uint8_t[]> data;
+
+ // The length of the binary data.
+ uint32_t data_length;
+
+ // The offset added to TargetValue#data_value (the index of the string in the inline string
+ // pool) in order to prevent the indices of the overlay resource table string pool from
+ // colliding with the inline string pool indices.
+ uint32_t string_pool_offset;
+ };
+
+ // The overlay's mapping of target resource name to overlaid value. Use a vector to enforce that
+ // the overlay pairs are inserted into the ResourceMapping in the specified ordered.
+ std::vector<Value> pairs;
+
+ // If the overlay maps a target resource to a string literal (not a string resource), then the
+ // this field contains information about the string pool in which the string literal resides so it
+ // can be inlined into an idmap.
+ std::optional<InlineStringPoolData> string_pool_data;
+};
+
+struct OverlayResourceContainer : public ResourceContainer {
+ static Result<std::unique_ptr<OverlayResourceContainer>> FromPath(std::string path);
+
+ WARN_UNUSED virtual Result<OverlayManifestInfo> FindOverlayInfo(
+ const std::string& name) const = 0;
+ WARN_UNUSED virtual Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const = 0;
+
+ ~OverlayResourceContainer() override = default;
+};
+
+} // namespace android::idmap2
+
+#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index f66916c..5a0a384 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -17,30 +17,22 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
#define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
+#include <androidfw/ApkAssets.h>
+
#include <map>
#include <memory>
#include <utility>
-#include "androidfw/ApkAssets.h"
+#include "idmap2/FabricatedOverlay.h"
#include "idmap2/LogInfo.h"
#include "idmap2/Policies.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/XmlParser.h"
-using android::idmap2::utils::OverlayManifestInfo;
-
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
namespace android::idmap2 {
-
-struct TargetValue {
- typedef uint8_t DataType;
- typedef uint32_t DataValue;
- DataType data_type;
- DataValue data_value;
-};
-
using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>;
using OverlayResourceMap = std::map<ResourceId, ResourceId>;
@@ -49,94 +41,60 @@
// Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
// `false` disables all overlayable and policy enforcement: this is intended for backwards
// compatibility pre-Q and unit tests.
- static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
- const ApkAssets& overlay_apk_assets,
- const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable, LogInfo& log_info);
+ static Result<ResourceMapping> FromContainers(const TargetResourceContainer& target,
+ const OverlayResourceContainer& overlay,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable, LogInfo& log_info);
// Retrieves the mapping of target resource id to overlay value.
- inline const TargetResourceMap& GetTargetToOverlayMap() const {
- return target_map_;
- }
+ WARN_UNUSED const TargetResourceMap& GetTargetToOverlayMap() const;
// Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
// an overlay resource to appear as a reference to its corresponding target resource at runtime.
- OverlayResourceMap GetOverlayToTargetMap() const;
-
- // Retrieves the build-time package id of the target package.
- inline uint32_t GetTargetPackageId() const {
- return target_package_id_;
- }
-
- // Retrieves the build-time package id of the overlay package.
- inline uint32_t GetOverlayPackageId() const {
- return overlay_package_id_;
- }
+ WARN_UNUSED const OverlayResourceMap& GetOverlayToTargetMap() const;
// Retrieves the offset that was added to the index of inline string overlay values so the indices
// do not collide with the indices of the overlay resource table string pool.
- inline uint32_t GetStringPoolOffset() const {
- return string_pool_offset_;
- }
+ WARN_UNUSED uint32_t GetStringPoolOffset() const;
// Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
- inline const StringPiece GetStringPoolData() const {
- return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
- string_pool_data_length_);
- }
+ WARN_UNUSED StringPiece GetStringPoolData() const;
private:
ResourceMapping() = default;
- // Maps a target resource id to an overlay resource id.
- // If rewrite_overlay_reference is `true` then references to the overlay
- // resource should appear as a reference to its corresponding target resource at runtime.
- Result<Unit> AddMapping(ResourceId target_resource, ResourceId overlay_resource,
- bool rewrite_overlay_reference);
-
- // Maps a target resource id to a data type and value combination.
- // The `data_type` is the runtime format of the data value (see Res_value::dataType).
- Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
- TargetValue::DataValue data_value);
-
- // Removes the overlay value mapping for the target resource.
- void RemoveMapping(ResourceId target_resource);
-
- // Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
- static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
- const LoadedPackage* target_package,
- const LoadedPackage* overlay_package,
- size_t string_pool_offset,
- const XmlParser& overlay_parser,
- LogInfo& log_info);
-
- // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
- // a target resource, a resource must exist in the overlay with the same type and entry name as
- // the target resource.
- static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
- const AssetManager2* overlay_am,
- const LoadedPackage* target_package,
- const LoadedPackage* overlay_package,
- LogInfo& log_info);
-
- // Removes resources that do not pass policy or overlayable checks of the target package.
- void FilterOverlayableResources(const AssetManager2* target_am,
- const LoadedPackage* target_package,
- const LoadedPackage* overlay_package,
- const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
+ // Maps a target resource id to an overlay resource id or a android::Res_value value.
+ //
+ // If `allow_rewriting_` is true, then the overlay-to-target map will be populated if the target
+ // resource id is mapped to an overlay resource id.
+ Result<Unit> AddMapping(ResourceId target_resource,
+ const std::variant<OverlayData::ResourceIdValue, TargetValue>& value);
TargetResourceMap target_map_;
- std::multimap<ResourceId, ResourceId> overlay_map_;
-
- uint32_t target_package_id_ = 0;
- uint32_t overlay_package_id_ = 0;
+ OverlayResourceMap overlay_map_;
uint32_t string_pool_offset_ = 0;
uint32_t string_pool_data_length_ = 0;
std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
};
+inline const TargetResourceMap& ResourceMapping::GetTargetToOverlayMap() const {
+ return target_map_;
+}
+
+inline const OverlayResourceMap& ResourceMapping::GetOverlayToTargetMap() const {
+ return overlay_map_;
+}
+
+inline uint32_t ResourceMapping::GetStringPoolOffset() const {
+ return string_pool_offset_;
+}
+
+inline StringPiece ResourceMapping::GetStringPoolData() const {
+ return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
+ string_pool_data_length_);
+}
+
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index cd14d3e..a0202df 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -22,19 +22,26 @@
#include "androidfw/AssetManager2.h"
#include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
namespace android::idmap2 {
-// use typedefs to let the compiler warn us about implicit casts
-typedef uint32_t ResourceId; // 0xpptteeee
-typedef uint8_t PackageId; // pp in 0xpptteeee
-typedef uint8_t TypeId; // tt in 0xpptteeee
-typedef uint16_t EntryId; // eeee in 0xpptteeee
-
#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
+// use typedefs to let the compiler warn us about implicit casts
+using ResourceId = uint32_t; // 0xpptteeee
+using PackageId = uint8_t; // pp in 0xpptteeee
+using TypeId = uint8_t; // tt in 0xpptteeee
+using EntryId = uint16_t; // eeee in 0xpptteeee
+
+using DataType = uint8_t; // Res_value::dataType
+using DataValue = uint32_t; // Res_value::data
+
+struct TargetValue {
+ DataType data_type;
+ DataValue data_value;
+};
+
namespace utils {
// Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
@@ -43,20 +50,10 @@
// Converts the Res_value::data_type to a human-readable string representation.
StringPiece DataTypeToString(uint8_t data_type);
-struct OverlayManifestInfo {
- std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
- std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
- std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
- uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
-};
-
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
- const std::string& name);
-
+// Retrieves the type and entry name of the resource in the AssetManager in the form type/entry.
Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
} // namespace utils
-
} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
index 1c74ab3..c968a5e 100644
--- a/cmds/idmap2/include/idmap2/XmlParser.h
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -30,8 +30,7 @@
namespace android::idmap2 {
-class XmlParser {
- public:
+struct XmlParser {
using Event = ResXMLParser::event_code_t;
class iterator;
@@ -127,23 +126,19 @@
};
// Creates a new xml parser beginning at the first tag.
- static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
- bool copy_data = false);
- ~XmlParser();
+ static Result<XmlParser> Create(const void* data, size_t size, bool copy_data = false);
inline iterator tree_iterator() const {
- return iterator(tree_);
+ return iterator(*tree_);
}
inline const ResStringPool& get_strings() const {
- return tree_.getStrings();
+ return tree_->getStrings();
}
private:
- XmlParser() = default;
- mutable ResXMLTree tree_;
-
- DISALLOW_COPY_AND_ASSIGN(XmlParser);
+ explicit XmlParser(std::unique_ptr<ResXMLTree> tree);
+ mutable std::unique_ptr<ResXMLTree> tree_;
};
} // namespace android::idmap2
diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h
deleted file mode 100644
index 8f50e36..0000000
--- a/cmds/idmap2/include/idmap2/ZipFile.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "idmap2/Result.h"
-#include "ziparchive/zip_archive.h"
-
-namespace android::idmap2 {
-
-struct MemoryChunk {
- size_t size;
- uint8_t buf[0];
-
- static std::unique_ptr<MemoryChunk> Allocate(size_t size);
-
- private:
- MemoryChunk() {
- }
-};
-
-class ZipFile {
- public:
- static std::unique_ptr<const ZipFile> Open(const std::string& path);
-
- std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
- Result<uint32_t> Crc(const std::string& entryPath) const;
-
- ~ZipFile();
-
- private:
- explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
- }
-
- const ::ZipArchiveHandle handle_;
-
- DISALLOW_COPY_AND_ASSIGN(ZipFile);
-};
-
-} // namespace android::idmap2
-
-#endif // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index c163107..3bbe9d9 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -87,10 +87,6 @@
}
void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
- Write8(header.GetTargetPackageId());
- Write8(header.GetOverlayPackageId());
- Write8(0U); // padding
- Write8(0U); // padding
Write32(header.GetTargetEntryCount());
Write32(header.GetTargetInlineEntryCount());
Write32(header.GetOverlayEntryCount());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
new file mode 100644
index 0000000..4f61801
--- /dev/null
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ */
+
+#include "idmap2/FabricatedOverlay.h"
+
+#include <androidfw/ResourceUtils.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <utils/ByteOrder.h>
+#include <zlib.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+namespace {
+bool Read32(std::istream& stream, uint32_t* out) {
+ uint32_t value;
+ if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
+ *out = dtohl(value);
+ return true;
+ }
+ return false;
+}
+
+void Write32(std::ostream& stream, uint32_t value) {
+ uint32_t x = htodl(value);
+ stream.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
+}
+} // namespace
+
+FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+ std::optional<uint32_t> crc_from_disk)
+ : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+}
+
+FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
+ const std::string& target_package_name) {
+ package_name_ = package_name;
+ name_ = name;
+ target_package_name_ = target_package_name;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetOverlayable(const std::string& name) {
+ target_overlayable_ = name;
+ return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+ const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
+ entries_.emplace_back(Entry{resource_name, data_type, data_value});
+ return *this;
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
+ std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+ for (const auto& res_entry : entries_) {
+ StringPiece package_substr;
+ StringPiece type_name;
+ StringPiece entry_name;
+ if (!android::ExtractResourceName(StringPiece(res_entry.resource_name), &package_substr,
+ &type_name, &entry_name)) {
+ return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
+ }
+
+ std::string package_name =
+ package_substr.empty() ? target_package_name_ : package_substr.to_string();
+ if (type_name.empty()) {
+ return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
+ }
+
+ if (entry_name.empty()) {
+ return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
+ }
+
+ auto package = entries.find(package_name);
+ if (package == entries.end()) {
+ package = entries
+ .insert(std::make_pair<>(
+ package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+ .first;
+ }
+
+ auto type = package->second.find(type_name.to_string());
+ if (type == package->second.end()) {
+ type =
+ package->second
+ .insert(std::make_pair<>(type_name.to_string(), std::map<std::string, TargetValue>()))
+ .first;
+ }
+
+ auto entry = type->second.find(entry_name.to_string());
+ if (entry == type->second.end()) {
+ entry = type->second.insert(std::make_pair<>(entry_name.to_string(), TargetValue())).first;
+ }
+
+ entry->second = TargetValue{res_entry.data_type, res_entry.data_value};
+ }
+
+ pb::FabricatedOverlay overlay_pb;
+ overlay_pb.set_package_name(package_name_);
+ overlay_pb.set_name(name_);
+ overlay_pb.set_target_package_name(target_package_name_);
+ overlay_pb.set_target_overlayable(target_overlayable_);
+
+ for (const auto& package : entries) {
+ auto package_pb = overlay_pb.add_packages();
+ package_pb->set_name(package.first);
+
+ for (const auto& type : package.second) {
+ auto type_pb = package_pb->add_types();
+ type_pb->set_name(type.first);
+
+ for (const auto& entry : type.second) {
+ auto entry_pb = type_pb->add_entries();
+ entry_pb->set_name(entry.first);
+ pb::ResourceValue* value = entry_pb->mutable_res_value();
+ value->set_data_type(entry.second.data_type);
+ value->set_data_value(entry.second.data_value);
+ }
+ }
+ }
+
+ return FabricatedOverlay(std::move(overlay_pb));
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
+ uint32_t magic;
+ if (!Read32(stream, &magic)) {
+ return Error("Failed to read fabricated overlay magic.");
+ }
+
+ if (magic != kFabricatedOverlayMagic) {
+ return Error("Not a fabricated overlay file.");
+ }
+
+ uint32_t version;
+ if (!Read32(stream, &version)) {
+ return Error("Failed to read fabricated overlay version.");
+ }
+
+ if (version != 1) {
+ return Error("Invalid fabricated overlay version '%u'.", version);
+ }
+
+ uint32_t crc;
+ if (!Read32(stream, &crc)) {
+ return Error("Failed to read fabricated overlay version.");
+ }
+
+ pb::FabricatedOverlay overlay{};
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
+ }
+
+ // If the proto version is the latest version, then the contents of the proto must be the same
+ // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
+ // proto to the latest version will likely change the contents of the fabricated overlay.
+ return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+ ? std::optional<uint32_t>(crc)
+ : std::nullopt);
+}
+
+Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
+ if (!data_.has_value()) {
+ auto size = overlay_pb_.ByteSizeLong();
+ auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+
+ // Ensure serialization is deterministic
+ google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+ google::protobuf::io::CodedOutputStream output_stream(&array_stream);
+ output_stream.SetSerializationDeterministic(true);
+ overlay_pb_.SerializeWithCachedSizes(&output_stream);
+ if (output_stream.HadError() || size != output_stream.ByteCount()) {
+ return Error("Failed to serialize fabricated overlay.");
+ }
+
+ // Calculate the crc using the proto data and the version.
+ uint32_t crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+ sizeof(uint32_t));
+ crc = crc32(crc, data.get(), size);
+ data_ = SerializedData{std::move(data), size, crc};
+ }
+ return &(*data_);
+}
+Result<uint32_t> FabricatedOverlay::GetCrc() const {
+ if (crc_from_disk_.has_value()) {
+ return *crc_from_disk_;
+ }
+ auto data = InitializeData();
+ if (!data) {
+ return data.GetError();
+ }
+ return (*data)->crc;
+}
+
+Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
+ auto data = InitializeData();
+ if (!data) {
+ return data.GetError();
+ }
+
+ Write32(stream, kFabricatedOverlayMagic);
+ Write32(stream, kFabricatedOverlayCurrentVersion);
+ Write32(stream, (*data)->crc);
+ stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+ if (stream.bad()) {
+ return Error("Failed to write serialized fabricated overlay.");
+ }
+
+ return Unit{};
+}
+
+using FabContainer = FabricatedOverlayContainer;
+FabContainer::FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path)
+ : overlay_(std::forward<FabricatedOverlay>(overlay)), path_(std::forward<std::string>(path)) {
+}
+
+FabContainer::~FabricatedOverlayContainer() = default;
+
+Result<std::unique_ptr<FabContainer>> FabContainer::FromPath(std::string path) {
+ std::fstream fin(path);
+ auto overlay = FabricatedOverlay::FromBinaryStream(fin);
+ if (!overlay) {
+ return overlay.GetError();
+ }
+ return std::unique_ptr<FabContainer>(
+ new FabricatedOverlayContainer(std::move(*overlay), std::move(path)));
+}
+
+std::unique_ptr<FabricatedOverlayContainer> FabContainer::FromOverlay(FabricatedOverlay&& overlay) {
+ return std::unique_ptr<FabContainer>(
+ new FabricatedOverlayContainer(std::move(overlay), {} /* path */));
+}
+
+OverlayManifestInfo FabContainer::GetManifestInfo() const {
+ const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+ return OverlayManifestInfo{
+ .package_name = overlay_pb.package_name(),
+ .name = overlay_pb.name(),
+ .target_package = overlay_pb.target_package_name(),
+ .target_name = overlay_pb.target_overlayable(),
+ };
+}
+
+Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
+ const OverlayManifestInfo info = GetManifestInfo();
+ if (name != info.name) {
+ return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
+ }
+ return info;
+}
+
+Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+ const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+ if (info.name != overlay_pb.name()) {
+ return Error("Failed to find name '%s' in fabricated overlay", info.name.c_str());
+ }
+
+ OverlayData result{};
+ for (const auto& package : overlay_pb.packages()) {
+ for (const auto& type : package.types()) {
+ for (const auto& entry : type.entries()) {
+ auto name = base::StringPrintf("%s:%s/%s", package.name().c_str(), type.name().c_str(),
+ entry.name().c_str());
+ const auto& res_value = entry.res_value();
+ result.pairs.emplace_back(OverlayData::Value{
+ name, TargetValue{.data_type = static_cast<uint8_t>(res_value.data_type()),
+ .data_value = res_value.data_value()}});
+ }
+ }
+ }
+ return result;
+}
+
+Result<uint32_t> FabContainer::GetCrc() const {
+ return overlay_.GetCrc();
+}
+
+const std::string& FabContainer::GetPath() const {
+ return path_;
+}
+
+Result<std::string> FabContainer::GetResourceName(ResourceId /* id */) const {
+ return Error("Fabricated overlay does not contain resources.");
+}
+
+} // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 3af1f70..98a4cea 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -47,4 +47,19 @@
}
#endif
+std::string RandomStringForPath(const size_t length) {
+ constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
+
+ std::string out_rand;
+ out_rand.reserve(length);
+
+ std::random_device rd;
+ std::uniform_int_distribution<int> dist(0, kCharLastIndex);
+ for (size_t i = 0; i < length; i++) {
+ out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
+ }
+ return out_rand;
+}
+
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index a0cc407..6515d55 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -20,23 +20,16 @@
#include <iostream>
#include <iterator>
#include <limits>
-#include <map>
#include <memory>
-#include <set>
#include <string>
#include <utility>
-#include <vector>
#include "android-base/macros.h"
-#include "android-base/stringprintf.h"
#include "androidfw/AssetManager2.h"
#include "idmap2/ResourceMapping.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
namespace android::idmap2 {
@@ -93,22 +86,13 @@
} // namespace
-Result<uint32_t> GetPackageCrc(const ZipFile& zip) {
- const Result<uint32_t> a = zip.Crc("resources.arsc");
- const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
- return a && b
- ? Result<uint32_t>(*a ^ *b)
- : Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc");
-}
-
std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_)) {
return nullptr;
}
- if (idmap_header->magic_ != kIdmapMagic ||
- idmap_header->version_ != kIdmapCurrentVersion) {
+ if (idmap_header->magic_ != kIdmapMagic || idmap_header->version_ != kIdmapCurrentVersion) {
// Do not continue parsing if the file is not a current version idmap.
return nullptr;
}
@@ -127,32 +111,22 @@
return std::move(idmap_header);
}
-Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
- const std::string& overlay_path,
+Result<Unit> IdmapHeader::IsUpToDate(const TargetResourceContainer& target,
+ const OverlayResourceContainer& overlay,
const std::string& overlay_name,
PolicyBitmask fulfilled_policies,
bool enforce_overlayable) const {
- const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
- if (!target_zip) {
- return Error("failed to open target %s", target_path.c_str());
- }
-
- const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
+ const Result<uint32_t> target_crc = target.GetCrc();
if (!target_crc) {
return Error("failed to get target crc");
}
- const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
- if (!overlay_zip) {
- return Error("failed to overlay target %s", overlay_path.c_str());
- }
-
- const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
+ const Result<uint32_t> overlay_crc = overlay.GetCrc();
if (!overlay_crc) {
return Error("failed to get overlay crc");
}
- return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc,
+ return IsUpToDate(target.GetPath(), overlay.GetPath(), overlay_name, *target_crc, *overlay_crc,
fulfilled_policies, enforce_overlayable);
}
@@ -209,11 +183,7 @@
std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
-
- uint8_t padding;
- if (!Read8(stream, &idmap_data_header->target_package_id_) ||
- !Read8(stream, &idmap_data_header->overlay_package_id_) || !Read8(stream, &padding) ||
- !Read8(stream, &padding) || !Read32(stream, &idmap_data_header->target_entry_count) ||
+ if (!Read32(stream, &idmap_data_header->target_entry_count) ||
!Read32(stream, &idmap_data_header->target_entry_inline_count) ||
!Read32(stream, &idmap_data_header->overlay_entry_count) ||
!Read32(stream, &idmap_data_header->string_pool_index_offset)) {
@@ -321,8 +291,6 @@
}
std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
- data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
- data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId();
data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
data_header->target_entry_inline_count =
static_cast<uint32_t>(data->target_inline_entries_.size());
@@ -332,57 +300,46 @@
return {std::move(data)};
}
-Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
- const ApkAssets& overlay_apk_assets,
- const std::string& overlay_name,
- const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable) {
+Result<std::unique_ptr<const Idmap>> Idmap::FromContainers(const TargetResourceContainer& target,
+ const OverlayResourceContainer& overlay,
+ const std::string& overlay_name,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable) {
SYSTRACE << "Idmap::FromApkAssets";
- const std::string& target_apk_path = target_apk_assets.GetPath();
- const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
-
- const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
- if (!target_zip) {
- return Error("failed to open target as zip");
- }
-
- const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
- if (!overlay_zip) {
- return Error("failed to open overlay as zip");
- }
-
std::unique_ptr<IdmapHeader> header(new IdmapHeader());
header->magic_ = kIdmapMagic;
header->version_ = kIdmapCurrentVersion;
- Result<uint32_t> crc = GetPackageCrc(*target_zip);
- if (!crc) {
- return Error(crc.GetError(), "failed to get zip CRC for target");
+ const auto target_crc = target.GetCrc();
+ if (!target_crc) {
+ return Error(target_crc.GetError(), "failed to get zip CRC for '%s'", target.GetPath().data());
}
- header->target_crc_ = *crc;
+ header->target_crc_ = *target_crc;
- crc = GetPackageCrc(*overlay_zip);
- if (!crc) {
- return Error(crc.GetError(), "failed to get zip CRC for overlay");
+ const auto overlay_crc = overlay.GetCrc();
+ if (!overlay_crc) {
+ return Error(overlay_crc.GetError(), "failed to get zip CRC for '%s'",
+ overlay.GetPath().data());
}
- header->overlay_crc_ = *crc;
+ header->overlay_crc_ = *overlay_crc;
+
header->fulfilled_policies_ = fulfilled_policies;
header->enforce_overlayable_ = enforce_overlayable;
- header->target_path_ = target_apk_path;
- header->overlay_path_ = overlay_apk_path;
+ header->target_path_ = target.GetPath();
+ header->overlay_path_ = overlay.GetPath();
header->overlay_name_ = overlay_name;
- auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name);
+ auto info = overlay.FindOverlayInfo(overlay_name);
if (!info) {
- return info.GetError();
+ return Error(info.GetError(), "failed to get overlay info for '%s'", overlay.GetPath().data());
}
LogInfo log_info;
- auto resource_mapping =
- ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info,
- fulfilled_policies, enforce_overlayable, log_info);
+ auto resource_mapping = ResourceMapping::FromContainers(
+ target, overlay, *info, fulfilled_policies, enforce_overlayable, log_info);
if (!resource_mapping) {
- return resource_mapping.GetError();
+ return Error(resource_mapping.GetError(), "failed to generate resource map for '%s'",
+ overlay.GetPath().data());
}
auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index 7e090a9..721612c 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -27,8 +27,6 @@
namespace android::idmap2 {
-#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
-
#define TAB " "
void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
@@ -36,8 +34,8 @@
void PrettyPrintVisitor::visit(const IdmapHeader& header) {
stream_ << "Paths:" << std::endl
- << TAB "target apk path : " << header.GetTargetPath() << std::endl
- << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
+ << TAB "target path : " << header.GetTargetPath() << std::endl
+ << TAB "overlay path : " << header.GetOverlayPath() << std::endl;
if (!header.GetOverlayName().empty()) {
stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
@@ -53,14 +51,11 @@
}
}
- if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) {
- target_am_.SetApkAssets({target_apk_.get()});
- apk_assets_.push_back(std::move(target_apk_));
+ if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+ target_ = std::move(*target);
}
-
- if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) {
- overlay_am_.SetApkAssets({overlay_apk.get()});
- apk_assets_.push_back(std::move(overlay_apk));
+ if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+ overlay_ = std::move(*overlay);
}
stream_ << "Mapping:" << std::endl;
@@ -72,23 +67,20 @@
void PrettyPrintVisitor::visit(const IdmapData& data) {
static constexpr const char* kUnknownResourceName = "???";
- const bool target_package_loaded = !target_am_.GetApkAssets().empty();
- const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
const ResStringPool string_pool(data.GetStringPoolData().data(), data.GetStringPoolData().size());
const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
for (const auto& target_entry : data.GetTargetEntries()) {
std::string target_name = kUnknownResourceName;
- if (target_package_loaded) {
- if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+ if (target_ != nullptr) {
+ if (auto name = target_->GetResourceName(target_entry.target_id)) {
target_name = *name;
}
}
std::string overlay_name = kUnknownResourceName;
- if (overlay_package_loaded) {
- if (auto name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id)) {
+ if (overlay_ != nullptr) {
+ if (auto name = overlay_->GetResourceName(target_entry.overlay_id)) {
overlay_name = *name;
}
}
@@ -112,8 +104,8 @@
}
std::string target_name = kUnknownResourceName;
- if (target_package_loaded) {
- if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+ if (target_ != nullptr) {
+ if (auto name = target_->GetResourceName(target_entry.target_id)) {
target_name = *name;
}
}
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index b517aa3..a016a36 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -18,16 +18,13 @@
#include <algorithm>
#include <cstdarg>
-#include <string>
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
-#include "androidfw/ApkAssets.h"
#include "idmap2/PolicyUtils.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
-using android::ApkAssets;
using android::idmap2::policy::PoliciesToDebugString;
namespace android::idmap2 {
@@ -48,27 +45,19 @@
print(header.GetOverlayName(), true /* print_value */, "overlay name");
print(header.GetDebugInfo(), false /* print_value */, "debug info");
- auto target_apk_ = ApkAssets::Load(header.GetTargetPath());
- if (target_apk_) {
- target_am_.SetApkAssets({target_apk_.get()});
- apk_assets_.push_back(std::move(target_apk_));
+ if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+ target_ = std::move(*target);
}
-
- auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath());
- if (overlay_apk_) {
- overlay_am_.SetApkAssets({overlay_apk_.get()});
- apk_assets_.push_back(std::move(overlay_apk_));
+ if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+ overlay_ = std::move(*overlay);
}
}
void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
- const bool target_package_loaded = !target_am_.GetApkAssets().empty();
- const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
for (auto& target_entry : data.GetTargetEntries()) {
Result<std::string> target_name(Error(""));
- if (target_package_loaded) {
- target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+ if (target_ != nullptr) {
+ target_name = target_->GetResourceName(target_entry.target_id);
}
if (target_name) {
print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -77,8 +66,8 @@
}
Result<std::string> overlay_name(Error(""));
- if (overlay_package_loaded) {
- overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id);
+ if (overlay_ != nullptr) {
+ overlay_name = overlay_->GetResourceName(target_entry.overlay_id);
}
if (overlay_name) {
print(target_entry.overlay_id, "overlay id: %s", overlay_name->c_str());
@@ -89,8 +78,8 @@
for (auto& target_entry : data.GetTargetInlineEntries()) {
Result<std::string> target_name(Error(""));
- if (target_package_loaded) {
- target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+ if (target_ != nullptr) {
+ target_name = target_->GetResourceName(target_entry.target_id);
}
if (target_name) {
print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -104,10 +93,10 @@
utils::DataTypeToString(target_entry.value.data_type).data());
Result<std::string> overlay_name(Error(""));
- if (overlay_package_loaded &&
+ if (overlay_ != nullptr &&
(target_entry.value.data_value == Res_value::TYPE_REFERENCE ||
target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
- overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.value.data_value);
+ overlay_name = overlay_->GetResourceName(target_entry.value.data_value);
}
if (overlay_name) {
@@ -119,8 +108,8 @@
for (auto& overlay_entry : data.GetOverlayEntries()) {
Result<std::string> overlay_name(Error(""));
- if (overlay_package_loaded) {
- overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id);
+ if (overlay_ != nullptr) {
+ overlay_name = overlay_->GetResourceName(overlay_entry.overlay_id);
}
if (overlay_name) {
@@ -130,8 +119,8 @@
}
Result<std::string> target_name(Error(""));
- if (target_package_loaded) {
- target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id);
+ if (target_ != nullptr) {
+ target_name = target_->GetResourceName(overlay_entry.target_id);
}
if (target_name) {
@@ -145,9 +134,6 @@
}
void RawPrintVisitor::visit(const IdmapData::Header& header) {
- print(header.GetTargetPackageId(), "target package id");
- print(header.GetOverlayPackageId(), "overlay package id");
- align();
print(header.GetTargetEntryCount(), "target entry count");
print(header.GetTargetInlineEntryCount(), "target inline entry count");
print(header.GetOverlayEntryCount(), "overlay entry count");
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
new file mode 100644
index 0000000..9147cca
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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.
+ */
+
+#include "idmap2/ResourceContainer.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/Util.h"
+#include "idmap2/FabricatedOverlay.h"
+#include "idmap2/XmlParser.h"
+
+namespace android::idmap2 {
+namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+ (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
+
+#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
+
+constexpr ResourceId kAttrName = 0x01010003;
+constexpr ResourceId kAttrResourcesMap = 0x01010609;
+constexpr ResourceId kAttrTargetName = 0x0101044d;
+constexpr ResourceId kAttrTargetPackage = 0x01010021;
+
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+const LoadedPackage* GetPackageAtIndex0(const LoadedArsc* loaded_arsc) {
+ const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
+ if (packages.empty()) {
+ return nullptr;
+ }
+ return loaded_arsc->GetPackageById(packages[0]->GetPackageId());
+}
+
+Result<uint32_t> CalculateCrc(const ZipAssetsProvider* zip_assets) {
+ constexpr const char* kResourcesArsc = "resources.arsc";
+ std::optional<uint32_t> res_crc = zip_assets->GetCrc(kResourcesArsc);
+ if (!res_crc) {
+ return Error("failed to get CRC for '%s'", kResourcesArsc);
+ }
+
+ constexpr const char* kManifest = "AndroidManifest.xml";
+ std::optional<uint32_t> man_crc = zip_assets->GetCrc(kManifest);
+ if (!man_crc) {
+ return Error("failed to get CRC for '%s'", kManifest);
+ }
+
+ return *res_crc ^ *man_crc;
+}
+
+Result<XmlParser> OpenXmlParser(const std::string& entry_path, const ZipAssetsProvider* zip) {
+ auto manifest = zip->Open(entry_path);
+ if (manifest == nullptr) {
+ return Error("failed to find %s ", entry_path.c_str());
+ }
+
+ auto size = manifest->getLength();
+ auto buffer = manifest->getIncFsBuffer(true /* aligned */).convert<uint8_t>();
+ if (!buffer.verify(size)) {
+ return Error("failed to read entire %s", entry_path.c_str());
+ }
+
+ return XmlParser::Create(buffer.unsafe_ptr(), size, true /* copyData */);
+}
+
+Result<XmlParser> OpenXmlParser(ResourceId id, const ZipAssetsProvider* zip,
+ const AssetManager2* am) {
+ const auto ref_table = am->GetDynamicRefTableForCookie(0);
+ if (ref_table == nullptr) {
+ return Error("failed to find dynamic ref table for cookie 0");
+ }
+
+ ref_table->lookupResourceId(&id);
+ auto value = am->GetResource(id);
+ if (!value.has_value()) {
+ return Error("failed to find resource for id 0x%08x", id);
+ }
+
+ if (value->type != Res_value::TYPE_STRING) {
+ return Error("resource for is 0x%08x is not a file", id);
+ }
+
+ auto string_pool = am->GetStringPoolForCookie(value->cookie);
+ auto file = string_pool->string8ObjectAt(value->data);
+ if (!file.has_value()) {
+ return Error("failed to find string for index %d", value->data);
+ }
+
+ return OpenXmlParser(file->c_str(), zip);
+}
+
+Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const ZipAssetsProvider* zip,
+ const std::string& name) {
+ Result<XmlParser> xml = OpenXmlParser("AndroidManifest.xml", zip);
+ if (!xml) {
+ return xml.GetError();
+ }
+
+ auto manifest_it = xml->tree_iterator();
+ if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
+ return Error("root element tag is not <manifest> in AndroidManifest.xml");
+ }
+
+ std::string package_name;
+ if (auto result_str = manifest_it->GetAttributeStringValue("package")) {
+ package_name = *result_str;
+ } else {
+ return result_str.GetError();
+ }
+
+ for (auto&& it : manifest_it) {
+ if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
+ continue;
+ }
+
+ OverlayManifestInfo info{};
+ info.package_name = package_name;
+ if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
+ if (*result_str != name) {
+ // A value for android:name was found, but either a the name does not match the requested
+ // name, or an <overlay> tag with no name was requested.
+ continue;
+ }
+ info.name = *result_str;
+ } else if (!name.empty()) {
+ // This tag does not have a value for android:name, but an <overlay> tag with a specific name
+ // has been requested.
+ continue;
+ }
+
+ if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
+ info.target_package = *result_str;
+ } else {
+ return Error("android:targetPackage missing from <overlay> in AndroidManifest.xml");
+ }
+
+ if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
+ info.target_name = *result_str;
+ }
+
+ if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
+ if (utils::IsReference((*result_value).dataType)) {
+ info.resource_mapping = (*result_value).data;
+ } else {
+ return Error("android:resourcesMap is not a reference in AndroidManifest.xml");
+ }
+ }
+ return info;
+ }
+
+ return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml", name.c_str());
+}
+
+Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider* zip,
+ const AssetManager2* overlay_am,
+ const LoadedArsc* overlay_arsc,
+ const LoadedPackage* overlay_package) {
+ auto parser = OpenXmlParser(id, zip, overlay_am);
+ if (!parser) {
+ return parser.GetError();
+ }
+
+ OverlayData overlay_data{};
+ const uint32_t string_pool_offset = overlay_arsc->GetStringPool()->size();
+ const uint8_t package_id = overlay_package->GetPackageId();
+ auto root_it = parser->tree_iterator();
+ if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
+ return Error("root element is not <overlay> tag");
+ }
+
+ auto overlay_it_end = root_it.end();
+ for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
+ if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
+ return Error("failed to parse overlay xml document");
+ }
+
+ if (overlay_it->event() != XmlParser::Event::START_TAG) {
+ continue;
+ }
+
+ if (overlay_it->name() != "item") {
+ return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+ }
+
+ Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
+ if (!target_resource) {
+ return Error(R"(<item> tag missing expected attribute "target")");
+ }
+
+ Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
+ if (!overlay_resource) {
+ return Error(R"(<item> tag missing expected attribute "value")");
+ }
+
+ if (overlay_resource->dataType == Res_value::TYPE_STRING) {
+ overlay_resource->data += string_pool_offset;
+ }
+
+ if (utils::IsReference(overlay_resource->dataType)) {
+ // Only rewrite resources defined within the overlay package to their corresponding target
+ // resource ids at runtime.
+ bool rewrite_id = package_id == EXTRACT_PACKAGE(overlay_resource->data);
+ overlay_data.pairs.emplace_back(OverlayData::Value{
+ *target_resource, OverlayData::ResourceIdValue{overlay_resource->data, rewrite_id}});
+ } else {
+ overlay_data.pairs.emplace_back(
+ OverlayData::Value{*target_resource, TargetValue{.data_type = overlay_resource->dataType,
+ .data_value = overlay_resource->data}});
+ }
+ }
+
+ const auto& string_pool = parser->get_strings();
+ const uint32_t string_pool_data_length = string_pool.bytes();
+ overlay_data.string_pool_data = OverlayData::InlineStringPoolData{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+ .data_length = string_pool_data_length,
+ .string_pool_offset = string_pool_offset,
+ };
+
+ // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
+ memcpy(overlay_data.string_pool_data->data.get(), string_pool.data().unsafe_ptr(),
+ string_pool_data_length);
+ return overlay_data;
+}
+
+OverlayData CreateResourceMappingLegacy(const AssetManager2* overlay_am,
+ const LoadedPackage* overlay_package) {
+ OverlayData overlay_data{};
+ for (const ResourceId overlay_resid : *overlay_package) {
+ if (auto name = utils::ResToTypeEntryName(*overlay_am, overlay_resid)) {
+ // Disable rewriting. Overlays did not support internal references before
+ // android:resourcesMap. Do not introduce new behavior.
+ overlay_data.pairs.emplace_back(OverlayData::Value{
+ *name, OverlayData::ResourceIdValue{overlay_resid, false /* rewrite_id */}});
+ }
+ }
+ return overlay_data;
+}
+
+struct ResState {
+ std::unique_ptr<ApkAssets> apk_assets;
+ const LoadedArsc* arsc;
+ const LoadedPackage* package;
+ std::unique_ptr<AssetManager2> am;
+ ZipAssetsProvider* zip_assets;
+
+ static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+ ResState state;
+ state.zip_assets = zip.get();
+ if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+ return Error("failed to load apk asset");
+ }
+
+ if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
+ return Error("failed to retrieve loaded arsc");
+ }
+
+ if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
+ return Error("failed to retrieve loaded package at index 0");
+ }
+
+ state.am = std::make_unique<AssetManager2>();
+ if (!state.am->SetApkAssets({state.apk_assets.get()})) {
+ return Error("failed to create asset manager");
+ }
+
+ return state;
+ }
+};
+
+} // namespace
+
+struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
+ static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+
+ // inherited from TargetResourceContainer
+ Result<bool> DefinesOverlayable() const override;
+ Result<const android::OverlayableInfo*> GetOverlayableInfo(ResourceId id) const override;
+ Result<ResourceId> GetResourceId(const std::string& name) const override;
+
+ // inherited from OverlayResourceContainer
+ Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+ Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+
+ // inherited from ResourceContainer
+ Result<uint32_t> GetCrc() const override;
+ Result<std::string> GetResourceName(ResourceId id) const override;
+ const std::string& GetPath() const override;
+
+ ~ApkResourceContainer() override = default;
+
+ private:
+ ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets, std::string path);
+
+ Result<const ResState*> GetState() const;
+ ZipAssetsProvider* GetZipAssets() const;
+
+ mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
+ std::string path_;
+};
+
+ApkResourceContainer::ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets,
+ std::string path)
+ : state_(std::move(zip_assets)), path_(std::move(path)) {
+}
+
+Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
+ const std::string& path) {
+ auto zip_assets = ZipAssetsProvider::Create(path);
+ if (zip_assets == nullptr) {
+ return Error("failed to load zip assets");
+ }
+ return std::unique_ptr<ApkResourceContainer>(
+ new ApkResourceContainer(std::move(zip_assets), path));
+}
+
+Result<const ResState*> ApkResourceContainer::GetState() const {
+ if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
+ return state;
+ }
+
+ auto state =
+ ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+ if (!state) {
+ return state.GetError();
+ }
+
+ state_ = std::move(*state);
+ return &std::get<ResState>(state_);
+}
+
+ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+ if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
+ return zip->get();
+ }
+ return std::get<ResState>(state_).zip_assets;
+}
+
+Result<bool> ApkResourceContainer::DefinesOverlayable() const {
+ auto state = GetState();
+ if (!state) {
+ return state.GetError();
+ }
+ return (*state)->package->DefinesOverlayable();
+}
+
+Result<const android::OverlayableInfo*> ApkResourceContainer::GetOverlayableInfo(
+ ResourceId id) const {
+ auto state = GetState();
+ if (!state) {
+ return state.GetError();
+ }
+ return (*state)->package->GetOverlayableInfo(id);
+}
+
+Result<OverlayManifestInfo> ApkResourceContainer::FindOverlayInfo(const std::string& name) const {
+ return ExtractOverlayManifestInfo(GetZipAssets(), name);
+}
+
+Result<OverlayData> ApkResourceContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+ const auto state = GetState();
+ if (!state) {
+ return state.GetError();
+ }
+
+ if (info.resource_mapping != 0) {
+ return CreateResourceMapping(info.resource_mapping, GetZipAssets(), (*state)->am.get(),
+ (*state)->arsc, (*state)->package);
+ }
+ return CreateResourceMappingLegacy((*state)->am.get(), (*state)->package);
+}
+
+Result<uint32_t> ApkResourceContainer::GetCrc() const {
+ return CalculateCrc(GetZipAssets());
+}
+
+const std::string& ApkResourceContainer::GetPath() const {
+ return path_;
+}
+
+Result<ResourceId> ApkResourceContainer::GetResourceId(const std::string& name) const {
+ auto state = GetState();
+ if (!state) {
+ return state.GetError();
+ }
+ auto id = (*state)->am->GetResourceId(name, "", (*state)->package->GetPackageName());
+ if (!id.has_value()) {
+ return Error("failed to find resource '%s'", name.c_str());
+ }
+
+ // Retrieve the compile-time resource id of the target resource.
+ return REWRITE_PACKAGE(*id, (*state)->package->GetPackageId());
+}
+
+Result<std::string> ApkResourceContainer::GetResourceName(ResourceId id) const {
+ auto state = GetState();
+ if (!state) {
+ return state.GetError();
+ }
+ return utils::ResToTypeEntryName(*(*state)->am, id);
+}
+
+Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
+ std::string path) {
+ auto result = ApkResourceContainer::FromPath(path);
+ if (!result) {
+ return result.GetError();
+ }
+ return std::unique_ptr<TargetResourceContainer>(result->release());
+}
+
+Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::FromPath(
+ std::string path) {
+ // Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
+ if (android::IsFabricatedOverlay(path)) {
+ auto result = FabricatedOverlayContainer::FromPath(path);
+ if (!result) {
+ return result.GetError();
+ }
+ return std::unique_ptr<OverlayResourceContainer>(result->release());
+ }
+
+ // Fallback to loading the container as an APK.
+ auto result = ApkResourceContainer::FromPath(path);
+ if (!result) {
+ return result.GetError();
+ }
+ return std::unique_ptr<OverlayResourceContainer>(result->release());
+}
+
+} // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 46eeb8e..3bbbf24 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -30,19 +30,12 @@
using android::base::StringPrintf;
using android::idmap2::utils::BitmaskToPolicies;
-using android::idmap2::utils::IsReference;
-using android::idmap2::utils::ResToTypeEntryName;
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
namespace {
-
-#define REWRITE_PACKAGE(resid, package_id) \
- (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
-#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
-
std::string ConcatPolicies(const std::vector<std::string>& policies) {
std::string message;
for (const std::string& policy : policies) {
@@ -55,11 +48,11 @@
return message;
}
-Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
+Result<Unit> CheckOverlayable(const TargetResourceContainer& target,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
const ResourceId& target_resource) {
- static constexpr const PolicyBitmask sDefaultPolicies =
+ constexpr const PolicyBitmask kDefaultPolicies =
PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
PolicyFlags::CONFIG_SIGNATURE;
@@ -68,8 +61,13 @@
// the overlay is preinstalled, signed with the same signature as the target or signed with the
// same signature as reference package defined in SystemConfig under 'overlay-config-signature'
// tag.
- if (!target_package.DefinesOverlayable()) {
- return (sDefaultPolicies & fulfilled_policies) != 0
+ const Result<bool> defines_overlayable = target.DefinesOverlayable();
+ if (!defines_overlayable) {
+ return Error(defines_overlayable.GetError(), "unable to retrieve overlayable info");
+ }
+
+ if (!*defines_overlayable) {
+ return (kDefaultPolicies & fulfilled_policies) != 0
? Result<Unit>({})
: Error(
"overlay must be preinstalled, signed with the same signature as the target,"
@@ -77,367 +75,112 @@
" <overlay-config-signature>.");
}
- const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
- if (overlayable_info == nullptr) {
+ const auto overlayable_info = target.GetOverlayableInfo(target_resource);
+ if (!overlayable_info) {
+ return overlayable_info.GetError();
+ }
+
+ if (*overlayable_info == nullptr) {
// Do not allow non-overlayable resources to be overlaid.
return Error("target resource has no overlayable declaration");
}
- if (overlay_info.target_name != overlayable_info->name) {
+ if (overlay_info.target_name != (*overlayable_info)->name) {
// If the overlay supplies a target overlayable name, the resource must belong to the
// overlayable defined with the specified name to be overlaid.
return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
- overlay_info.target_name.c_str(), overlayable_info->name.c_str());
+ overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
}
// Enforce policy restrictions if the resource is declared as overlayable.
- if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
+ if (((*overlayable_info)->policy_flags & fulfilled_policies) == 0) {
return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
- ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
+ ConcatPolicies(BitmaskToPolicies((*overlayable_info)->policy_flags)).c_str());
}
return Result<Unit>({});
}
-// TODO(martenkongstad): scan for package name instead of assuming package at index 0
-//
-// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
-// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
-// this assumption tends to work out. That said, the correct thing to do is to scan
-// resources.arsc for a package with a given name as read from the package manifest instead of
-// relying on a hard-coded index. This however requires storing the package name in the idmap
-// header, which in turn requires incrementing the idmap version. Because the initial version of
-// idmap2 is compatible with idmap, this will have to wait for now.
-const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
- const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
- if (packages.empty()) {
- return nullptr;
+std::string GetDebugResourceName(const ResourceContainer& container, ResourceId resid) {
+ auto name = container.GetResourceName(resid);
+ if (name) {
+ return *name;
}
- int id = packages[0]->GetPackageId();
- return loaded_arsc.GetPackageById(id);
+ return StringPrintf("0x%08x", resid);
}
-
-Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
- const AssetManager2& asset_manager) {
- auto value = asset_manager.GetResource(resource_id);
- if (!value.has_value()) {
- return Error("failed to find resource for id 0x%08x", resource_id);
- }
-
- if (value->type != Res_value::TYPE_STRING) {
- return Error("resource for is 0x%08x is not a file", resource_id);
- }
-
- auto string_pool = asset_manager.GetStringPoolForCookie(value->cookie);
- auto file = string_pool->string8ObjectAt(value->data);
- if (!file.has_value()) {
- return Error("failed to find string for index %d", value->data);
- }
-
- // Load the overlay resource mappings from the file specified using android:resourcesMap.
- auto asset = asset_manager.OpenNonAsset(file->c_str(), Asset::AccessMode::ACCESS_BUFFER);
- if (asset == nullptr) {
- return Error("file \"%s\" not found", file->c_str());
- }
-
- return asset;
-}
-
} // namespace
-Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
- const LoadedPackage* target_package,
- const LoadedPackage* overlay_package,
- size_t string_pool_offset,
- const XmlParser& overlay_parser,
- LogInfo& log_info) {
- ResourceMapping resource_mapping;
- auto root_it = overlay_parser.tree_iterator();
- if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
- return Error("root element is not <overlay> tag");
+Result<ResourceMapping> ResourceMapping::FromContainers(const TargetResourceContainer& target,
+ const OverlayResourceContainer& overlay,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable,
+ LogInfo& log_info) {
+ auto overlay_data = overlay.GetOverlayData(overlay_info);
+ if (!overlay_data) {
+ return overlay_data.GetError();
}
- const uint8_t target_package_id = target_package->GetPackageId();
- const uint8_t overlay_package_id = overlay_package->GetPackageId();
- auto overlay_it_end = root_it.end();
- for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
- if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
- return Error("failed to parse overlay xml document");
- }
-
- if (overlay_it->event() != XmlParser::Event::START_TAG) {
+ ResourceMapping mapping;
+ for (const auto& overlay_pair : overlay_data->pairs) {
+ const auto target_resid = target.GetResourceId(overlay_pair.resource_name);
+ if (!target_resid) {
+ log_info.Warning(LogMessage() << target_resid.GetErrorMessage());
continue;
}
- if (overlay_it->name() != "item") {
- return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+ if (enforce_overlayable) {
+ // Filter out resources the overlay is not allowed to override.
+ auto overlayable = CheckOverlayable(target, overlay_info, fulfilled_policies, *target_resid);
+ if (!overlayable) {
+ log_info.Warning(LogMessage() << "overlay '" << overlay.GetPath()
+ << "' is not allowed to overlay resource '"
+ << GetDebugResourceName(target, *target_resid)
+ << "' in target: " << overlayable.GetErrorMessage());
+ continue;
+ }
}
- Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
- if (!target_resource) {
- return Error(R"(<item> tag missing expected attribute "target")");
- }
-
- Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
- if (!overlay_resource) {
- return Error(R"(<item> tag missing expected attribute "value")");
- }
-
- auto target_id_result =
- target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
- if (!target_id_result.has_value()) {
- log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
- << "\" in target resources");
- continue;
- }
-
- // Retrieve the compile-time resource id of the target resource.
- uint32_t target_id = REWRITE_PACKAGE(*target_id_result, target_package_id);
-
- if (overlay_resource->dataType == Res_value::TYPE_STRING) {
- overlay_resource->data += string_pool_offset;
- }
-
- if (IsReference(overlay_resource->dataType)) {
- // Only rewrite resources defined within the overlay package to their corresponding target
- // resource ids at runtime.
- bool rewrite_reference = overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data);
- resource_mapping.AddMapping(target_id, overlay_resource->data, rewrite_reference);
- } else {
- resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data);
+ if (auto result = mapping.AddMapping(*target_resid, overlay_pair.value); !result) {
+ return Error(result.GetError(), "failed to add mapping for '%s'",
+ GetDebugResourceName(target, *target_resid).c_str());
}
}
- return resource_mapping;
+ auto& string_pool_data = overlay_data->string_pool_data;
+ if (string_pool_data.has_value()) {
+ mapping.string_pool_offset_ = string_pool_data->string_pool_offset;
+ mapping.string_pool_data_ = std::move(string_pool_data->data);
+ mapping.string_pool_data_length_ = string_pool_data->data_length;
+ }
+
+ return std::move(mapping);
}
-Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
- const AssetManager2* target_am, const AssetManager2* overlay_am,
- const LoadedPackage* target_package, const LoadedPackage* overlay_package, LogInfo& log_info) {
- ResourceMapping resource_mapping;
- const uint8_t target_package_id = target_package->GetPackageId();
- const auto end = overlay_package->end();
- for (auto iter = overlay_package->begin(); iter != end; ++iter) {
- const ResourceId overlay_resid = *iter;
- Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
- if (!name) {
- continue;
+Result<Unit> ResourceMapping::AddMapping(
+ ResourceId target_resource,
+ const std::variant<OverlayData::ResourceIdValue, TargetValue>& value) {
+ if (target_map_.find(target_resource) != target_map_.end()) {
+ return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+ }
+
+ // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
+ // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
+
+ if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) {
+ target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id));
+ if (overlay_resource->rewrite_id) {
+ // An overlay resource can override multiple target resources at once. Rewrite the overlay
+ // resource as the first target resource it overrides.
+ overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
}
-
- // Find the resource with the same type and entry name within the target package.
- const std::string full_name =
- base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
- auto target_resource_result = target_am->GetResourceId(full_name);
- if (!target_resource_result.has_value()) {
- log_info.Warning(LogMessage()
- << "failed to find resource \"" << full_name << "\" in target resources");
- continue;
- }
-
- // Retrieve the compile-time resource id of the target resource.
- ResourceId target_resource = REWRITE_PACKAGE(*target_resource_result, target_package_id);
- resource_mapping.AddMapping(target_resource, overlay_resid,
- false /* rewrite_overlay_reference */);
- }
-
- return resource_mapping;
-}
-
-void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
- const LoadedPackage* target_package,
- const LoadedPackage* overlay_package,
- const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies,
- LogInfo& log_info) {
- std::set<ResourceId> remove_ids;
- for (const auto& target_map : target_map_) {
- const ResourceId target_resid = target_map.first;
- Result<Unit> success =
- CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
- if (success) {
- continue;
- }
-
- // Attempting to overlay a resource that is not allowed to be overlaid is treated as a
- // warning.
- Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
- if (!name) {
- name = StringPrintf("0x%08x", target_resid);
- }
-
- log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
- << "\" is not allowed to overlay resource \"" << *name
- << "\" in target: " << success.GetErrorMessage());
-
- remove_ids.insert(target_resid);
- }
-
- for (const ResourceId target_resid : remove_ids) {
- RemoveMapping(target_resid);
- }
-}
-
-Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
- const ApkAssets& overlay_apk_assets,
- const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable,
- LogInfo& log_info) {
- AssetManager2 target_asset_manager;
- if (!target_asset_manager.SetApkAssets({&target_apk_assets})) {
- return Error("failed to create target asset manager");
- }
-
- AssetManager2 overlay_asset_manager;
- if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets})) {
- return Error("failed to create overlay asset manager");
- }
-
- const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
- if (target_arsc == nullptr) {
- return Error("failed to load target resources.arsc");
- }
-
- const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
- if (overlay_arsc == nullptr) {
- return Error("failed to load overlay resources.arsc");
- }
-
- const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
- if (target_pkg == nullptr) {
- return Error("failed to load target package from resources.arsc");
- }
-
- const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
- if (overlay_pkg == nullptr) {
- return Error("failed to load overlay package from resources.arsc");
- }
-
- size_t string_pool_data_length = 0U;
- size_t string_pool_offset = 0U;
- std::unique_ptr<uint8_t[]> string_pool_data;
- Result<ResourceMapping> resource_mapping = {{}};
- if (overlay_info.resource_mapping != 0U) {
- // Use the dynamic reference table to find the assigned resource id of the map xml.
- const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
- uint32_t resource_mapping_id = overlay_info.resource_mapping;
- ref_table->lookupResourceId(&resource_mapping_id);
-
- // Load the overlay resource mappings from the file specified using android:resourcesMap.
- auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
- if (!asset) {
- return Error("failed opening xml for android:resourcesMap: %s",
- asset.GetErrorMessage().c_str());
- }
-
- auto parser =
- XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
- if (!parser) {
- return Error("failed opening ResXMLTree");
- }
-
- // Copy the xml string pool data before the parse goes out of scope.
- auto& string_pool = (*parser)->get_strings();
- string_pool_data_length = string_pool.bytes();
- string_pool_data.reset(new uint8_t[string_pool_data_length]);
-
- // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
- memcpy(string_pool_data.get(), string_pool.data().unsafe_ptr(), string_pool_data_length);
-
- // Offset string indices by the size of the overlay resource table string pool.
- string_pool_offset = overlay_arsc->GetStringPool()->size();
-
- resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
- string_pool_offset, *(*parser), log_info);
} else {
- // If no file is specified using android:resourcesMap, it is assumed that the overlay only
- // defines resources intended to override target resources of the same type and name.
- resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
- target_pkg, overlay_pkg, log_info);
+ auto overlay_value = std::get<TargetValue>(value);
+ target_map_.insert(std::make_pair(target_resource, overlay_value));
}
- if (!resource_mapping) {
- return resource_mapping.GetError();
- }
-
- if (enforce_overlayable) {
- // Filter out resources the overlay is not allowed to override.
- (*resource_mapping)
- .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
- fulfilled_policies, log_info);
- }
-
- resource_mapping->target_package_id_ = target_pkg->GetPackageId();
- resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
- resource_mapping->string_pool_offset_ = string_pool_offset;
- resource_mapping->string_pool_data_ = std::move(string_pool_data);
- resource_mapping->string_pool_data_length_ = string_pool_data_length;
- return std::move(*resource_mapping);
-}
-
-OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
- // An overlay resource can override multiple target resources at once. Rewrite the overlay
- // resource as the first target resource it overrides.
- OverlayResourceMap map;
- for (const auto& mappings : overlay_map_) {
- map.insert(std::make_pair(mappings.first, mappings.second));
- }
- return map;
-}
-
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, ResourceId overlay_resource,
- bool rewrite_overlay_reference) {
- if (target_map_.find(target_resource) != target_map_.end()) {
- return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
- }
-
- // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
- // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
- target_map_.insert(std::make_pair(target_resource, overlay_resource));
-
- if (rewrite_overlay_reference) {
- overlay_map_.insert(std::make_pair(overlay_resource, target_resource));
- }
return Unit{};
}
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
- TargetValue::DataType data_type,
- TargetValue::DataValue data_value) {
- if (target_map_.find(target_resource) != target_map_.end()) {
- return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
- }
-
- // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
- // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
- target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
- return Unit{};
-}
-
-void ResourceMapping::RemoveMapping(ResourceId target_resource) {
- auto target_iter = target_map_.find(target_resource);
- if (target_iter == target_map_.end()) {
- return;
- }
-
- const auto value = target_iter->second;
- target_map_.erase(target_iter);
-
- const ResourceId* overlay_resource = std::get_if<ResourceId>(&value);
- if (overlay_resource == nullptr) {
- return;
- }
-
- auto overlay_iter = overlay_map_.equal_range(*overlay_resource);
- for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
- if (i->second == target_resource) {
- overlay_map_.erase(i);
- return;
- }
- }
-}
-
} // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index 4e85e57..e809bf1 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -17,27 +17,16 @@
#include "idmap2/ResourceUtils.h"
#include <memory>
-#include <string>
#include "androidfw/StringPiece.h"
#include "androidfw/Util.h"
#include "idmap2/Result.h"
-#include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
using android::StringPiece16;
using android::idmap2::Result;
-using android::idmap2::XmlParser;
-using android::idmap2::ZipFile;
using android::util::Utf16ToUtf8;
namespace android::idmap2::utils {
-namespace {
-constexpr ResourceId kAttrName = 0x01010003;
-constexpr ResourceId kAttrResourcesMap = 0x01010609;
-constexpr ResourceId kAttrTargetName = 0x0101044d;
-constexpr ResourceId kAttrTargetPackage = 0x01010021;
-} // namespace
bool IsReference(uint8_t data_type) {
return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
@@ -97,71 +86,4 @@
return out;
}
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
- const std::string& name) {
- std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
- if (!zip) {
- return Error("failed to open %s as a zip file", path.c_str());
- }
-
- std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
- if (!entry) {
- return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
- }
-
- Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
- if (!xml) {
- return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
- }
-
- auto manifest_it = (*xml)->tree_iterator();
- if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
- return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
- }
-
- for (auto&& it : manifest_it) {
- if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
- continue;
- }
-
- OverlayManifestInfo info{};
- if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
- if (*result_str != name) {
- // A value for android:name was found, but either a the name does not match the requested
- // name, or an <overlay> tag with no name was requested.
- continue;
- }
- info.name = *result_str;
- } else if (!name.empty()) {
- // This tag does not have a value for android:name, but an <overlay> tag with a specific name
- // has been requested.
- continue;
- }
-
- if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
- info.target_package = *result_str;
- } else {
- return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
- result_str.GetErrorMessage().c_str());
- }
-
- if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
- info.target_name = *result_str;
- }
-
- if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
- if (IsReference((*result_value).dataType)) {
- info.resource_mapping = (*result_value).data;
- } else {
- return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
- path.c_str());
- }
- }
- return info;
- }
-
- return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s",
- name.c_str(), path.c_str());
-}
-
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 00baea4..70822c8 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -151,16 +151,18 @@
return value ? GetStringValue(parser_, *value, name) : value.GetError();
}
-Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
- bool copy_data) {
- auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
- if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
+XmlParser::XmlParser(std::unique_ptr<ResXMLTree> tree) : tree_(std::move(tree)) {
+}
+
+Result<XmlParser> XmlParser::Create(const void* data, size_t size, bool copy_data) {
+ auto tree = std::make_unique<ResXMLTree>();
+ if (tree->setTo(data, size, copy_data) != NO_ERROR) {
return Error("Malformed xml block");
}
// Find the beginning of the first tag.
XmlParser::Event event;
- while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+ while ((event = tree->next()) != XmlParser::Event::BAD_DOCUMENT &&
event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
}
@@ -172,11 +174,7 @@
return Error("Bad xml document");
}
- return parser;
-}
-
-XmlParser::~XmlParser() {
- tree_.uninit();
+ return XmlParser{std::move(tree)};
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
deleted file mode 100644
index 1e1a218..0000000
--- a/cmds/idmap2/libidmap2/ZipFile.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "idmap2/ZipFile.h"
-
-#include <memory>
-#include <string>
-
-#include "idmap2/Result.h"
-
-namespace android::idmap2 {
-
-std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
- void* ptr = ::operator new(sizeof(MemoryChunk) + size);
- std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
- chunk->size = size;
- return chunk;
-}
-
-std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
- ::ZipArchiveHandle handle;
- int32_t status = ::OpenArchive(path.c_str(), &handle);
- if (status != 0) {
- ::CloseArchive(handle);
- return nullptr;
- }
- return std::unique_ptr<ZipFile>(new ZipFile(handle));
-}
-
-ZipFile::~ZipFile() {
- ::CloseArchive(handle_);
-}
-
-std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
- ::ZipEntry entry;
- int32_t status = ::FindEntry(handle_, entryPath, &entry);
- if (status != 0) {
- return nullptr;
- }
- std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
- status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
- if (status != 0) {
- return nullptr;
- }
- return chunk;
-}
-
-Result<uint32_t> ZipFile::Crc(const std::string& entryPath) const {
- ::ZipEntry entry;
- int32_t status = ::FindEntry(handle_, entryPath, &entry);
- if (status != 0) {
- return Error("failed to find zip entry %s", entryPath.c_str());
- }
- return entry.crc32;
-}
-
-} // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/proto/fabricated_v1.proto b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
new file mode 100644
index 0000000..a392b2b
--- /dev/null
+++ b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package android.idmap2.pb;
+
+option optimize_for = LITE_RUNTIME;
+
+// All changes to the proto messages in this file MUST be backwards compatible. Backwards
+// incompatible changes will cause previously fabricated overlays to be considered corrupt by the
+// new proto message specification.
+message FabricatedOverlay {
+ repeated ResourcePackage packages = 1;
+ string name = 2;
+ string package_name = 3;
+ string target_package_name = 4;
+ string target_overlayable = 5;
+}
+
+message ResourcePackage {
+ string name = 1;
+ repeated ResourceType types = 2;
+}
+
+message ResourceType {
+ string name = 1;
+ repeated ResourceEntry entries = 2;
+}
+
+message ResourceEntry {
+ string name = 1;
+ oneof value {
+ ResourceValue res_value = 2;
+ }
+}
+
+message ResourceValue {
+ // Corresponds with android::Res_value::dataType
+ uint32 data_type = 1;
+ // Corresponds with android::Res_value::data
+ uint32 data_value = 2;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index 524aabc..bf63327 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -33,7 +33,7 @@
namespace android::idmap2 {
TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
auto result1 = Idmap::FromBinaryStream(raw_stream);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
new file mode 100644
index 0000000..79ab243
--- /dev/null
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <idmap2/FabricatedOverlay.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+TEST(FabricatedOverlayTests, OverlayInfo) {
+ auto overlay =
+ FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+ .SetOverlayable("TestResources")
+ .Build();
+
+ ASSERT_TRUE(overlay);
+ auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+ auto info = container->FindOverlayInfo("SandTheme");
+ ASSERT_TRUE(info);
+ EXPECT_EQ("SandTheme", (*info).name);
+ EXPECT_EQ("TestResources", (*info).target_name);
+
+ info = container->FindOverlayInfo("OceanTheme");
+ ASSERT_FALSE(info);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValue) {
+ auto overlay =
+ FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+ .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+ .SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
+ .SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .Build();
+ ASSERT_TRUE(overlay);
+ auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+ auto info = container->FindOverlayInfo("SandTheme");
+ ASSERT_TRUE(info);
+ ASSERT_TRUE((*info).target_name.empty());
+
+ auto crc = (*container).GetCrc();
+ ASSERT_TRUE(crc) << crc.GetErrorMessage();
+ EXPECT_NE(0U, *crc);
+
+ auto pairs = container->GetOverlayData(*info);
+ ASSERT_TRUE(pairs);
+ EXPECT_FALSE(pairs->string_pool_data.has_value());
+ ASSERT_EQ(3U, pairs->pairs.size());
+
+ auto& it = pairs->pairs[0];
+ ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ auto entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(1U, entry->data_value);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+
+ it = pairs->pairs[1];
+ ASSERT_EQ("com.example.target:string/int3", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(0x7f010000, entry->data_value);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
+
+ it = pairs->pairs[2];
+ ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(2U, entry->data_value);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
+ {
+ auto builder =
+ FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+ .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U);
+ ASSERT_FALSE(builder.Build());
+ }
+ {
+ auto builder =
+ FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+ .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U);
+ ASSERT_FALSE(builder.Build());
+ }
+}
+
+TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
+ auto overlay =
+ FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+ .SetOverlayable("TestResources")
+ .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+ .Build();
+ ASSERT_TRUE(overlay);
+ TemporaryFile tf;
+ std::ofstream out(tf.path);
+ ASSERT_TRUE((*overlay).ToBinaryStream(out));
+ out.close();
+
+ auto container = OverlayResourceContainer::FromPath(tf.path);
+ ASSERT_TRUE(container) << container.GetErrorMessage();
+ EXPECT_EQ(tf.path, (*container)->GetPath());
+
+ auto crc = (*container)->GetCrc();
+ ASSERT_TRUE(crc) << crc.GetErrorMessage();
+ EXPECT_NE(0U, *crc);
+
+ auto info = (*container)->FindOverlayInfo("SandTheme");
+ ASSERT_TRUE(info) << info.GetErrorMessage();
+ EXPECT_EQ("SandTheme", (*info).name);
+ EXPECT_EQ("TestResources", (*info).target_name);
+
+ auto pairs = (*container)->GetOverlayData(*info);
+ ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
+ EXPECT_EQ(1U, pairs->pairs.size());
+
+ auto& it = pairs->pairs[0];
+ ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+ auto entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(1U, entry->data_value);
+ EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+} // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 16b68f0..9516ff8 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <android-base/file.h>
+
#include <cstdio> // fclose
#include <fstream>
#include <memory>
@@ -61,12 +63,12 @@
}
TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
ASSERT_THAT(header, NotNull());
ASSERT_EQ(header->GetMagic(), 0x504d4449U);
- ASSERT_EQ(header->GetVersion(), 0x07U);
+ ASSERT_EQ(header->GetVersion(), 0x08U);
ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -81,7 +83,7 @@
std::stringstream stream;
stream << android::kIdmapMagic;
stream << 0xffffffffU;
- stream << std::string(kJunkSize, (char) 0xffU);
+ stream << std::string(kJunkSize, (char)0xffU);
ASSERT_FALSE(Idmap::FromBinaryStream(stream));
}
@@ -90,14 +92,13 @@
std::stringstream stream;
stream << 0xffffffffU;
stream << android::kIdmapCurrentVersion;
- stream << std::string(kJunkSize, (char) 0xffU);
+ stream << std::string(kJunkSize, (char)0xffU);
ASSERT_FALSE(Idmap::FromBinaryStream(stream));
}
TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
const size_t offset = kIdmapRawDataOffset;
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
- kIdmapRawDataLen - offset);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
std::istringstream stream(raw);
std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
@@ -108,8 +109,7 @@
TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
const size_t offset = kIdmapRawDataOffset;
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
- kIdmapRawDataLen - offset);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
std::istringstream stream(raw);
std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
@@ -134,7 +134,7 @@
}
TEST(IdmapTests, CreateIdmapFromBinaryStream) {
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
auto result = Idmap::FromBinaryStream(stream);
@@ -143,7 +143,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -177,7 +177,7 @@
}
TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData),
10); // data too small
std::istringstream stream(raw);
@@ -189,14 +189,14 @@
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
- auto idmap_result = Idmap::FromApkAssets(
- *target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
+ auto idmap_result = Idmap::FromContainers(
+ **target, **overlay, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
@@ -204,7 +204,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
@@ -218,15 +218,15 @@
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
- auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
- TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ auto idmap_result = Idmap::FromContainers(
+ **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
ASSERT_THAT(idmap, NotNull());
@@ -255,25 +255,66 @@
ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
}
+TEST(IdmapTests, FabricatedOverlay) {
+ std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
+
+ auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+ .SetOverlayable("TestResources")
+ .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+ .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .Build();
+
+ ASSERT_TRUE(frro);
+ TemporaryFile tf;
+ std::ofstream out(tf.path);
+ ASSERT_TRUE((*frro).ToBinaryStream(out));
+ out.close();
+
+ auto overlay = OverlayResourceContainer::FromPath(tf.path);
+ ASSERT_TRUE(overlay);
+
+ auto idmap_result = Idmap::FromContainers(**target, **overlay, "SandTheme", PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
+ ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+ auto& idmap = *idmap_result;
+ ASSERT_THAT(idmap, NotNull());
+
+ const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+ ASSERT_EQ(dataBlocks.size(), 1U);
+
+ const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+ ASSERT_THAT(data, NotNull());
+ ASSERT_EQ(data->GetTargetEntries().size(), 0U);
+ ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
+
+ const auto& target_inline_entries = data->GetTargetInlineEntries();
+ ASSERT_EQ(target_inline_entries.size(), 2U);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
+ Res_value::TYPE_INT_DEC, 2U);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
+ Res_value::TYPE_REFERENCE, 0x7f010000);
+}
+
TEST(IdmapTests, FailCreateIdmapInvalidName) {
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
{
- auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ auto idmap_result = Idmap::FromContainers(**target, **overlay, "", PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_FALSE(idmap_result);
}
{
- auto idmap_result =
- Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ auto idmap_result = Idmap::FromContainers(**target, **overlay, "unknown", PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_FALSE(idmap_result);
}
}
@@ -282,15 +323,15 @@
std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
- auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
- TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ auto idmap_result = Idmap::FromContainers(
+ **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
auto& idmap = *idmap_result;
ASSERT_THAT(idmap, NotNull());
@@ -328,30 +369,29 @@
}
Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
- const std::string& local_target_apk_path, const std::string& local_overlay_apk_path,
+ const std::string& local_target_path, const std::string& local_overlay_path,
const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
- auto overlay_info =
- utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+ const std::string target_path(GetTestDataPath() + local_target_path);
+ auto target = TargetResourceContainer::FromPath(target_path);
+ if (!target) {
+ return Error(R"(Failed to load target "%s")", target_path.c_str());
+ }
+
+ const std::string overlay_path(GetTestDataPath() + local_overlay_path);
+ auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+ if (!overlay) {
+ return Error(R"(Failed to load overlay "%s")", overlay_path.c_str());
+ }
+
+ auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
if (!overlay_info) {
- return overlay_info.GetError();
- }
-
- const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- if (!target_apk) {
- return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
- }
-
- const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- if (!overlay_apk) {
- return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+ return Error(R"(Failed to find overlay name "%s")", overlay_name.c_str());
}
LogInfo log_info;
- auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
- fulfilled_policies, enforce_overlayable, log_info);
+ auto mapping = ResourceMapping::FromContainers(**target, **overlay, *overlay_info,
+ fulfilled_policies, enforce_overlayable, log_info);
if (!mapping) {
return mapping.GetError();
}
@@ -360,11 +400,9 @@
}
TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
- auto idmap_data =
- TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages",
-
- PolicyFlags::PUBLIC,
- /* enforce_overlayable */ false);
+ auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk",
+ "DifferentPackages", PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ false);
ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
auto& data = *idmap_data;
@@ -417,7 +455,7 @@
const uint32_t target_crc = kIdmapRawDataTargetCrc;
const uint32_t overlay_crc = kIdmapRawOverlayCrc;
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
auto result = Idmap::FromBinaryStream(raw_stream);
@@ -468,8 +506,8 @@
ASSERT_THAT(bad_target_crc_header, NotNull());
ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
ASSERT_FALSE(bad_target_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
- target_crc, overlay_crc, policies,
- /* enforce_overlayable */ true));
+ target_crc, overlay_crc, policies,
+ /* enforce_overlayable */ true));
// overlay crc: bytes (0xc, 0xf)
std::string bad_overlay_crc_string(stream.str());
@@ -483,8 +521,8 @@
ASSERT_THAT(bad_overlay_crc_header, NotNull());
ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
- target_crc, overlay_crc, policies,
- /* enforce_overlayable */ true));
+ target_crc, overlay_crc, policies,
+ /* enforce_overlayable */ true));
// fulfilled policy: bytes (0x10, 0x13)
std::string bad_policy_string(stream.str());
@@ -522,8 +560,8 @@
ASSERT_THAT(bad_target_path_header, NotNull());
ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
ASSERT_FALSE(bad_target_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
- target_crc, overlay_crc, policies,
- /* enforce_overlayable */ true));
+ target_crc, overlay_crc, policies,
+ /* enforce_overlayable */ true));
// overlay path: bytes (0x2c, 0x37)
std::string bad_overlay_path_string(stream.str());
@@ -576,7 +614,7 @@
};
TEST(IdmapTests, TestVisitor) {
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream stream(raw);
const auto idmap = Idmap::FromBinaryStream(stream);
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index 87ce0f1..3d3d82a 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -27,35 +27,31 @@
#include "idmap2/Idmap.h"
#include "idmap2/PrettyPrintVisitor.h"
-using android::ApkAssets;
using android::base::StringPrintf;
-using ::testing::NotNull;
-using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
- const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk,
- TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+ PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
PrettyPrintVisitor visitor(stream);
(*idmap)->accept(&visitor);
- ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
- ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("target path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n",
R::target::integer::int1, R::overlay::integer::int1)),
std::string::npos);
@@ -64,7 +60,7 @@
TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
fclose(stderr); // silence expected warnings from libandroidfw
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -74,8 +70,8 @@
PrettyPrintVisitor visitor(stream);
(*idmap)->accept(&visitor);
- ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
- ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("target path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000 (\?\?\? -> \?\?\?)\n"), std::string::npos);
}
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 88f85ef..a6371cb 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -30,17 +30,16 @@
#include "idmap2/RawPrintVisitor.h"
using android::base::StringPrintf;
-using ::testing::NotNull;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
namespace android::idmap2 {
-#define ASSERT_CONTAINS_REGEX(pattern, str) \
- do { \
- ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \
- << "pattern '" << pattern << "' not found in\n--------\n" \
- << str << "--------"; \
+#define ASSERT_CONTAINS_REGEX(pattern, str) \
+ do { \
+ ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \
+ << "pattern '" << (pattern) << "' not found in\n--------\n" \
+ << (str) << "--------"; \
} while (0)
#define ADDRESS "[0-9a-f]{8}: "
@@ -49,16 +48,15 @@
fclose(stderr); // silence expected warnings
const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- ASSERT_THAT(target_apk, NotNull());
+ auto target = TargetResourceContainer::FromPath(target_apk_path);
+ ASSERT_TRUE(target);
const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- ASSERT_THAT(overlay_apk, NotNull());
+ auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+ ASSERT_TRUE(overlay);
- const auto idmap =
- Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT,
- PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
+ const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+ PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
@@ -66,7 +64,7 @@
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str());
ASSERT_CONTAINS_REGEX(
StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
stream.str());
@@ -75,8 +73,6 @@
stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 fulfilled policies: public\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 enforce overlayable\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000000 target inline entry count", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count", stream.str());
@@ -104,7 +100,7 @@
TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
fclose(stderr); // silence expected warnings from libandroidfw
- std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+ std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
std::istringstream raw_stream(raw);
const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -115,7 +111,7 @@
(*idmap)->accept(&visitor);
ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS "00000007 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000008 version\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str());
@@ -126,8 +122,6 @@
ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay path: overlayX.apk\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "0000000b overlay name size\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "........ overlay name: OverlayName\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str());
- ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000001 target inline entry count\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str());
@@ -140,7 +134,7 @@
ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "7f030002 target id\n", stream.str());
ASSERT_CONTAINS_REGEX(ADDRESS "00000004 string pool size\n", stream.str());
- ASSERT_CONTAINS_REGEX("000000a8: ........ string pool\n", stream.str());
+ ASSERT_CONTAINS_REGEX("000000a4: ........ string pool\n", stream.str());
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 0362529..5a1d808 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include <android-base/file.h>
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
#include <cstdio> // fclose
#include <fstream>
#include <memory>
@@ -22,14 +26,10 @@
#include "R.h"
#include "TestConstants.h"
#include "TestHelpers.h"
-#include "androidfw/ResourceTypes.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
#include "idmap2/LogInfo.h"
#include "idmap2/ResourceMapping.h"
using android::Res_value;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
@@ -41,32 +41,36 @@
ASSERT_TRUE(result) << result.GetErrorMessage(); \
} while (0)
-Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path,
- const std::string& local_overlay_apk_path,
+Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_path,
+ const std::string& local_overlay_path,
const std::string& overlay_name,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
- auto overlay_info =
- ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+ const std::string target_path = (local_target_path[0] == '/')
+ ? local_target_path
+ : (GetTestDataPath() + "/" + local_target_path);
+ auto target = TargetResourceContainer::FromPath(target_path);
+ if (!target) {
+ return Error(target.GetError(), R"(Failed to load target "%s")", target_path.c_str());
+ }
+
+ const std::string overlay_path = (local_overlay_path[0] == '/')
+ ? local_overlay_path
+ : (GetTestDataPath() + "/" + local_overlay_path);
+ auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+ if (!overlay) {
+ return Error(overlay.GetError(), R"(Failed to load overlay "%s")", overlay_path.c_str());
+ }
+
+ auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
if (!overlay_info) {
- return overlay_info.GetError();
- }
-
- const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
- std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
- if (!target_apk) {
- return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
- }
-
- const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
- std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
- if (!overlay_apk) {
- return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+ return Error(overlay_info.GetError(), R"(Failed to find overlay name "%s")",
+ overlay_name.c_str());
}
LogInfo log_info;
- return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
- fulfilled_policies, enforce_overlayable, log_info);
+ return ResourceMapping::FromContainers(**target, **overlay, *overlay_info, fulfilled_policies,
+ enforce_overlayable, log_info);
}
Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource,
@@ -128,7 +132,7 @@
}
TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
ASSERT_TRUE(resources) << resources.GetErrorMessage();
@@ -145,7 +149,7 @@
}
TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "SwapNames",
PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -161,7 +165,7 @@
}
TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
"DifferentPackages", PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -176,7 +180,7 @@
}
TEST(ResourceMappingTests, InlineResources) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "Inline",
PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
constexpr size_t overlay_string_pool_size = 10U;
@@ -189,8 +193,32 @@
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U));
}
+TEST(ResourceMappingTests, FabricatedOverlay) {
+ auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+ .SetOverlayable("TestResources")
+ .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+ .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .Build();
+
+ ASSERT_TRUE(frro);
+ TemporaryFile tf;
+ std::ofstream out(tf.path);
+ ASSERT_TRUE((*frro).ToBinaryStream(out));
+ out.close();
+
+ auto resources = TestGetResourceMapping("target/target.apk", tf.path, "SandTheme",
+ PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+ ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
+ ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+ ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
+}
+
TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
@@ -209,7 +237,7 @@
// Resources that are not declared as overlayable and resources that a protected by policies the
// overlay does not fulfill must not map to overlay resources.
TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ true);
@@ -229,7 +257,7 @@
// overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
// off.
TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES,
PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -264,7 +292,7 @@
// Overlays that do not target an <overlayable> tag can overlay any resource if overlayable
// enforcement is disabled.
TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
- auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+ auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
PolicyFlags::PUBLIC,
/* enforce_overlayable */ false);
@@ -284,10 +312,9 @@
// Overlays that are neither pre-installed nor signed with the same signature as the target cannot
// overlay packages that have not defined overlayable resources.
TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
- auto resources =
- TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
- "NoTargetName", PolicyFlags::PUBLIC,
- /* enforce_overlayable */ true);
+ auto resources = TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
+ "NoTargetName", PolicyFlags::PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(resources) << resources.GetErrorMessage();
ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
@@ -297,9 +324,9 @@
// signed with the same signature as the reference package can overlay packages that have not
// defined overlayable resources.
TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
- auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
+ auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) {
auto resources =
- TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
+ TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies,
/* enforce_overlayable */ true);
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
index 1f6bf49..6914208 100644
--- a/cmds/idmap2/tests/ResourceUtilsTests.cpp
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -17,10 +17,12 @@
#include <memory>
#include <string>
+#include "R.h"
#include "TestHelpers.h"
#include "androidfw/ApkAssets.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "idmap2/ResourceContainer.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
@@ -49,8 +51,8 @@
};
TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
- Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000U);
- ASSERT_TRUE(name);
+ Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), R::target::integer::int1);
+ ASSERT_TRUE(name) << name.GetErrorMessage();
ASSERT_EQ(*name, "integer/int1");
}
@@ -60,25 +62,34 @@
}
TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) {
- auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
- "InvalidName");
+ auto overlay =
+ OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+ ASSERT_TRUE(overlay);
+
+ auto info = (*overlay)->FindOverlayInfo("InvalidName");
ASSERT_FALSE(info);
}
TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) {
- auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
- "ValidName");
+ auto overlay =
+ OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+ ASSERT_TRUE(overlay);
+
+ auto info = (*overlay)->FindOverlayInfo("ValidName");
ASSERT_FALSE(info);
}
TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) {
- auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
- "ValidNameAndTargetPackage");
+ auto overlay =
+ OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+ ASSERT_TRUE(overlay);
+
+ auto info = (*overlay)->FindOverlayInfo("ValidNameAndTargetPackage");
ASSERT_TRUE(info);
ASSERT_EQ("ValidNameAndTargetPackage", info->name);
ASSERT_EQ("Valid", info->target_package);
- ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
- ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
+ ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
+ ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
}
-}// namespace android::idmap2
+} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 842af3d..6b5f3a8 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -24,13 +24,13 @@
namespace android::idmap2 {
-const unsigned char idmap_raw_data[] = {
+const unsigned char kIdmapRawData[] = {
// IDMAP HEADER
// 0x0: magic
0x49, 0x44, 0x4d, 0x50,
// 0x4: version
- 0x07, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00,
// 0x8: target crc
0x34, 0x12, 0x00, 0x00,
@@ -70,81 +70,72 @@
0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
// DATA HEADER
- // 0x54: target_package_id
- 0x7f,
-
- // 0x55: overlay_package_id
- 0x7f,
-
- // 0x56: padding
- 0x00, 0x00,
-
- // 0x58: target_entry_count
+ // 0x54: target_entry_count
0x03, 0x00, 0x00, 0x00,
- // 0x5c: target_inline_entry_count
+ // 0x58: target_inline_entry_count
0x01, 0x00, 0x00, 0x00,
- // 0x60: overlay_entry_count
+ // 0x5c: overlay_entry_count
0x03, 0x00, 0x00, 0x00,
- // 0x64: string_pool_offset
+ // 0x60: string_pool_offset
0x00, 0x00, 0x00, 0x00,
// TARGET ENTRIES
- // 0x68: target id (0x7f020000)
+ // 0x64: target id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
- // 0x6c: overlay_id (0x7f020000)
+ // 0x68: overlay_id (0x7f020000)
0x00, 0x00, 0x02, 0x7f,
- // 0x70: target id (0x7f030000)
+ // 0x6c: target id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
- // 0x74: overlay_id (0x7f030000)
+ // 0x70: overlay_id (0x7f030000)
0x00, 0x00, 0x03, 0x7f,
- // 0x78: target id (0x7f030002)
+ // 0x74: target id (0x7f030002)
0x02, 0x00, 0x03, 0x7f,
- // 0x7c: overlay_id (0x7f030001)
+ // 0x78: overlay_id (0x7f030001)
0x01, 0x00, 0x03, 0x7f,
// INLINE TARGET ENTRIES
- // 0x80: target_id
+ // 0x7c: target_id
0x00, 0x00, 0x04, 0x7f,
- // 0x84: Res_value::size (value ignored by idmap)
+ // 0x80: Res_value::size (value ignored by idmap)
0x08, 0x00,
- // 0x87: Res_value::res0 (value ignored by idmap)
+ // 0x82: Res_value::res0 (value ignored by idmap)
0x00,
- // 0x88: Res_value::dataType (TYPE_INT_HEX)
+ // 0x83: Res_value::dataType (TYPE_INT_HEX)
0x11,
- // 0x8c: Res_value::data
+ // 0x84: Res_value::data
0x78, 0x56, 0x34, 0x12,
// OVERLAY ENTRIES
- // 0x90: 0x7f020000 -> 0x7f020000
+ // 0x88: 0x7f020000 -> 0x7f020000
0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
- // 0x98: 0x7f030000 -> 0x7f030000
+ // 0x90: 0x7f030000 -> 0x7f030000
0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
- // 0xa0: 0x7f030001 -> 0x7f030002
+ // 0x98: 0x7f030001 -> 0x7f030002
0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
- // 0xa4: string pool
+ // 0xa0: string pool
// string length,
0x04, 0x00, 0x00, 0x00,
- // 0xa8 string contents "test"
+ // 0xa4 string contents "test"
0x74, 0x65, 0x73, 0x74};
-const unsigned int kIdmapRawDataLen = 0xac;
+const unsigned int kIdmapRawDataLen = 0xa8;
const unsigned int kIdmapRawDataOffset = 0x54;
const unsigned int kIdmapRawDataTargetCrc = 0x1234;
const unsigned int kIdmapRawOverlayCrc = 0x5678;
diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp
index 1a7eaca..eaf10a7 100644
--- a/cmds/idmap2/tests/XmlParserTests.cpp
+++ b/cmds/idmap2/tests/XmlParserTests.cpp
@@ -19,25 +19,25 @@
#include <string>
#include "TestHelpers.h"
-#include "gmock/gmock.h"
+#include "androidfw/AssetsProvider.h"
#include "gtest/gtest.h"
#include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
namespace android::idmap2 {
-Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+Result<XmlParser> CreateTestParser(const std::string& test_file) {
+ auto zip = ZipAssetsProvider::Create(GetTestDataPath() + "/target/target.apk");
if (zip == nullptr) {
return Error("Failed to open zip file");
}
- auto data = zip->Uncompress(test_file);
+ auto data = zip->Open(test_file);
if (data == nullptr) {
return Error("Failed to open xml file");
}
- return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
+ return XmlParser::Create(data->getBuffer(true /* aligned*/), data->getLength(),
+ /* copy_data */ true);
}
TEST(XmlParserTests, Create) {
@@ -54,7 +54,7 @@
auto xml = CreateTestParser("res/xml/test.xml");
ASSERT_TRUE(xml) << xml.GetErrorMessage();
- auto root_iter = (*xml)->tree_iterator();
+ auto root_iter = xml->tree_iterator();
ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
ASSERT_EQ(root_iter->name(), "a");
@@ -85,7 +85,7 @@
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
- auto root_iter = (*xml)->tree_iterator();
+ auto root_iter = xml->tree_iterator();
// Start at the <b> tag.
auto a_iter = root_iter.begin();
@@ -111,8 +111,8 @@
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
- auto root_iter_1 = (*xml)->tree_iterator();
- auto root_iter_2 = (*xml)->tree_iterator();
+ auto root_iter_1 = xml->tree_iterator();
+ auto root_iter_2 = xml->tree_iterator();
ASSERT_EQ(root_iter_1, root_iter_2);
ASSERT_EQ(*root_iter_1, *root_iter_2);
@@ -146,7 +146,7 @@
ASSERT_TRUE(xml) << xml.GetErrorMessage();
// Start at the <a> tag.
- auto root_iter_1 = (*xml)->tree_iterator();
+ auto root_iter_1 = xml->tree_iterator();
// Start at the <b> tag.
auto a_iter_1 = root_iter_1.begin();
diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp
deleted file mode 100644
index 3fca436..0000000
--- a/cmds/idmap2/tests/ZipFileTests.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdio> // fclose
-#include <string>
-
-#include "TestHelpers.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace android::idmap2 {
-
-TEST(ZipFileTests, BasicOpen) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
- ASSERT_THAT(zip, NotNull());
-
- fclose(stderr); // silence expected warnings from libziparchive
- auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
- ASSERT_THAT(fail, IsNull());
-}
-
-TEST(ZipFileTests, Crc) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
- ASSERT_THAT(zip, NotNull());
-
- Result<uint32_t> crc = zip->Crc("AndroidManifest.xml");
- ASSERT_TRUE(crc);
- ASSERT_EQ(*crc, 0x762f3d24);
-
- Result<uint32_t> crc2 = zip->Crc("does-not-exist");
- ASSERT_FALSE(crc2);
-}
-
-TEST(ZipFileTests, Uncompress) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
- ASSERT_THAT(zip, NotNull());
-
- auto data = zip->Uncompress("assets/lorem-ipsum.txt");
- ASSERT_THAT(data, NotNull());
- const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
- ASSERT_THAT(data->size, lorem_ipsum.size());
- ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
-
- auto fail = zip->Uncompress("does-not-exist");
- ASSERT_THAT(fail, IsNull());
-}
-
-} // namespace android::idmap2
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 90a526b..48aa8b2 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -204,7 +204,6 @@
Landroid/os/IUpdateEngine$Stub;-><init>()V
Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager;
-Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService;
Landroid/os/storage/IObbActionListener$Stub;-><init>()V
Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager;
diff --git a/core/api/current.txt b/core/api/current.txt
index f82e000..8757830 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -573,6 +573,7 @@
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
+ field public static final int edgeEffectType = 16844329; // 0x1010629
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -848,6 +849,7 @@
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
+ field public static final int knownCerts = 16844330; // 0x101062a
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
@@ -5585,10 +5587,10 @@
field public static final String EXTRA_PROGRESS = "android.progress";
field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
- field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+ field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
field @Deprecated public static final String EXTRA_SMALL_ICON = "android.icon";
@@ -6894,6 +6896,7 @@
method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int);
+ method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean);
method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent);
method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle);
method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -7070,6 +7073,7 @@
method public boolean isProfileOwnerApp(String);
method public boolean isProvisioningAllowed(@NonNull String);
method public boolean isResetPasswordTokenActive(android.content.ComponentName);
+ method public boolean isSafeOperation(int);
method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
method public boolean isUniqueDeviceAttestationSupported();
@@ -7242,7 +7246,7 @@
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 public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
+ 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_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
@@ -7298,6 +7302,7 @@
field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+ field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
@@ -7334,7 +7339,6 @@
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
- field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7488,7 +7492,7 @@
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
method public int describeContents();
- method public int getReason();
+ method @NonNull public java.util.List<java.lang.Integer> getReasons();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
}
@@ -10439,6 +10443,7 @@
field public static final String USAGE_STATS_SERVICE = "usagestats";
field public static final String USB_SERVICE = "usb";
field public static final String USER_SERVICE = "user";
+ field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
field public static final String VIBRATOR_SERVICE = "vibrator";
field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
field public static final String WALLPAPER_SERVICE = "wallpaper";
@@ -10979,6 +10984,7 @@
field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+ field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
@@ -21890,6 +21896,7 @@
field public static final String KEY_COLOR_RANGE = "color-range";
field public static final String KEY_COLOR_STANDARD = "color-standard";
field public static final String KEY_COLOR_TRANSFER = "color-transfer";
+ field public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
field public static final String KEY_COMPLEXITY = "complexity";
field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
field public static final String KEY_DURATION = "durationUs";
@@ -23032,10 +23039,12 @@
method @Deprecated public int getStreamType();
method public String getTitle(android.content.Context);
method public float getVolume();
+ method public boolean isHapticGeneratorEnabled();
method public boolean isLooping();
method public boolean isPlaying();
method public void play();
method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+ method public boolean setHapticGeneratorEnabled(boolean);
method public void setLooping(boolean);
method @Deprecated public void setStreamType(int);
method public void setVolume(float);
@@ -31624,6 +31633,7 @@
method @NonNull public int[] areEffectsSupported(@NonNull int...);
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+ method public int getId();
method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
@@ -31638,10 +31648,12 @@
}
public abstract class VibratorManager {
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
method @NonNull public abstract android.os.Vibrator getVibrator(int);
method @NonNull public abstract int[] getVibratorIds();
- method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -38065,6 +38077,10 @@
method public final void setNotificationsShown(String[]);
method public final void snoozeNotification(String, long);
method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+ field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
+ field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
+ field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
+ field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4
field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38073,6 +38089,7 @@
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+ field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types";
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3
field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
@@ -42028,8 +42045,9 @@
field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
- field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+ field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
}
public class TelephonyManager {
@@ -53581,20 +53599,27 @@
public class EdgeEffect {
ctor public EdgeEffect(android.content.Context);
+ ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
method public boolean draw(android.graphics.Canvas);
method public void finish();
method @Nullable public android.graphics.BlendMode getBlendMode();
method @ColorInt public int getColor();
+ method public float getDistance();
method public int getMaxHeight();
+ method public int getType();
method public boolean isFinished();
method public void onAbsorb(int);
method public void onPull(float);
method public void onPull(float, float);
+ method public float onPullDistance(float, float);
method public void onRelease();
method public void setBlendMode(@Nullable android.graphics.BlendMode);
method public void setColor(@ColorInt int);
method public void setSize(int, int);
+ method public void setType(int);
field public static final android.graphics.BlendMode DEFAULT_BLEND_MODE;
+ field public static final int TYPE_GLOW = 0; // 0x0
+ field public static final int TYPE_STRETCH = 1; // 0x1
}
public class EditText extends android.widget.TextView {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 74ccbb1..7436cdc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -179,6 +179,7 @@
field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+ field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -338,9 +339,8 @@
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
- field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+ field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
field public static final int config_systemGallery = 17039399; // 0x1040027
- field public static final int config_systemVideoCall = 17039401; // 0x1040029
}
public static final class R.style {
@@ -905,8 +905,8 @@
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
- field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
- field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
+ field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
+ field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
@@ -2523,7 +2523,8 @@
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
- field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+ field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+ field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version";
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
@@ -2632,6 +2633,7 @@
field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
+ field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -7044,11 +7046,15 @@
method public long getExpiryTimeMillis();
method public long getRefreshTimeMillis();
method @Nullable public android.net.Uri getUserPortalUrl();
+ method public int getUserPortalUrlSource();
method @Nullable public String getVenueFriendlyName();
method @Nullable public android.net.Uri getVenueInfoUrl();
+ method public int getVenueInfoUrlSource();
method public boolean isCaptive();
method public boolean isSessionExtendable();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
}
@@ -7062,8 +7068,10 @@
method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+ method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+ method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
}
public class ConnectivityManager {
@@ -7182,6 +7190,7 @@
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 {
@@ -9512,10 +9521,12 @@
}
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+ method @Nullable public int[] getAttestationIds();
method public int getNamespace();
}
public static final class KeyGenParameterSpec.Builder {
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
}
@@ -11940,6 +11951,7 @@
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
+ field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 661379b..694507d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -14,6 +14,7 @@
field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
+ field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES";
field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
@@ -53,9 +54,8 @@
field public static final int config_defaultAssistant = 17039393; // 0x1040021
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
- field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+ field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
field public static final int config_systemGallery = 17039399; // 0x1040027
- field public static final int config_systemVideoCall = 17039401; // 0x1040029
}
}
@@ -391,11 +391,11 @@
method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
method public boolean isCurrentInputMethodSetByOwner();
method public boolean isFactoryResetProtectionPolicySupported();
+ method @NonNull public static String operationSafetyReasonToString(int);
method @NonNull public static String operationToString(int);
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
- method @NonNull public static String unsafeOperationReasonToString(int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
@@ -425,6 +425,7 @@
field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c
field public static final int OPERATION_REMOVE_USER = 6; // 0x6
field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d
+ field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff
field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e
field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
@@ -460,7 +461,6 @@
field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
- field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
}
public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
@@ -693,6 +693,7 @@
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
method public void holdLock(android.os.IBinder, int);
+ method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
@@ -913,6 +914,8 @@
method @NonNull public int[] getSupportedStates();
method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+ field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
+ field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
}
public static interface DeviceStateManager.DeviceStateListener {
@@ -1371,6 +1374,32 @@
field public static final int RESOURCES_SDK_INT;
}
+ public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+ method public abstract long getDuration();
+ }
+
+ public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
+ method public long getDuration();
+ method @NonNull public android.os.VibrationEffect getEffect();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
+ }
+
+ public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
+ method @NonNull public java.util.List<java.lang.Integer> getDelays();
+ method public long getDuration();
+ method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
+ }
+
+ public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
+ method public long getDuration();
+ method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
+ }
+
public class DeviceIdleManager {
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/core/java/android/accounts/OWNERS
+++ b/core/java/android/accounts/OWNERS
@@ -3,7 +3,6 @@
sandrakwan@google.com
hackbod@google.com
svetoslavganov@google.com
-moltmann@google.com
fkupolov@google.com
yamasani@google.com
omakoto@google.com
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 43d0269..e2426d1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -50,7 +50,9 @@
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
+import android.hardware.HardwareBuffer;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
@@ -76,6 +78,7 @@
import android.util.Size;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
+import android.window.TaskSnapshot;
import com.android.internal.app.LocalePicker;
import com.android.internal.app.procstats.ProcessStats;
@@ -1740,6 +1743,62 @@
*/
public static class RecentTaskInfo extends TaskInfo implements Parcelable {
/**
+ * @hide
+ */
+ public static class PersistedTaskSnapshotData {
+ /**
+ * The bounds of the task when the last snapshot was taken, may be null if the task is
+ * not yet attached to the hierarchy.
+ * @see {@link android.window.TaskSnapshot#mTaskSize}.
+ * @hide
+ */
+ public @Nullable Point taskSize;
+
+ /**
+ * The content insets of the task when the task snapshot was taken.
+ * @see {@link android.window.TaskSnapshot#mContentInsets}.
+ * @hide
+ */
+ public @Nullable Rect contentInsets;
+
+ /**
+ * The size of the last snapshot taken, may be null if there is no associated snapshot.
+ * @see {@link android.window.TaskSnapshot#mSnapshot}.
+ * @hide
+ */
+ public @Nullable Point bufferSize;
+
+ /**
+ * Sets the data from the other data.
+ * @hide
+ */
+ public void set(PersistedTaskSnapshotData other) {
+ taskSize = other.taskSize;
+ contentInsets = other.contentInsets;
+ bufferSize = other.bufferSize;
+ }
+
+ /**
+ * Sets the data from the provided {@param snapshot}.
+ * @hide
+ */
+ public void set(TaskSnapshot snapshot) {
+ if (snapshot == null) {
+ taskSize = null;
+ contentInsets = null;
+ bufferSize = null;
+ return;
+ }
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ taskSize = new Point(snapshot.getTaskSize());
+ contentInsets = new Rect(snapshot.getContentInsets());
+ bufferSize = buffer != null
+ ? new Point(buffer.getWidth(), buffer.getHeight())
+ : null;
+ }
+ }
+
+ /**
* If this task is currently running, this is the identifier for it.
* If it is not running, this will be -1.
*
@@ -1782,6 +1841,12 @@
*/
public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
+ /**
+ * Information about the last snapshot taken for this task.
+ * @hide
+ */
+ public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
+
public RecentTaskInfo() {
}
@@ -1798,6 +1863,9 @@
id = source.readInt();
persistentId = source.readInt();
childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
+ lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
+ lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
+ lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
super.readFromParcel(source);
}
@@ -1806,6 +1874,9 @@
dest.writeInt(id);
dest.writeInt(persistentId);
dest.writeList(childrenTaskInfos);
+ dest.writeTypedObject(lastSnapshotData.taskSize, flags);
+ dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
+ dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
super.writeToParcel(dest, flags);
}
@@ -1825,7 +1896,6 @@
public void dump(PrintWriter pw, String indent) {
pw.println(); pw.print(" ");
pw.print(" id="); pw.print(persistentId);
- pw.print(" stackId="); pw.print(stackId);
pw.print(" userId="); pw.print(userId);
pw.print(" hasTask="); pw.print((id != -1));
pw.print(" lastActiveTime="); pw.println(lastActiveTime);
@@ -1872,6 +1942,12 @@
pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
pw.println(" }");
}
+ pw.print(" ");
+ pw.print(" lastSnapshotData {");
+ pw.print(" taskSize=" + lastSnapshotData.taskSize);
+ pw.print(" contentInsets=" + lastSnapshotData.contentInsets);
+ pw.print(" bufferSize=" + lastSnapshotData.bufferSize);
+ pw.println(" }");
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c242fd4..b6fc47f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1208,7 +1208,8 @@
* of a {@link BigPictureStyle} notification. This will replace a
* {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
*/
- public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
+ public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
+ "android.showBigPictureWhenCollapsed";
/**
* {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
@@ -7059,7 +7060,7 @@
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
- private boolean mPromotePicture;
+ private boolean mShowBigPictureWhenCollapsed;
public BigPictureStyle() {
}
@@ -7124,7 +7125,7 @@
*/
@NonNull
public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
- mPromotePicture = show;
+ mShowBigPictureWhenCollapsed = show;
return this;
}
@@ -7195,7 +7196,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (mPicture == null || !mPromotePicture) {
+ if (mPicture == null || !mShowBigPictureWhenCollapsed) {
return super.makeContentView(increasedHeight);
}
@@ -7225,7 +7226,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (mPicture == null || !mPromotePicture) {
+ if (mPicture == null || !mShowBigPictureWhenCollapsed) {
return super.makeHeadsUpContentView(increasedHeight);
}
@@ -7309,7 +7310,7 @@
extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
mPictureContentDescription);
}
- extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture);
+ extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
extras.putParcelable(EXTRA_PICTURE, mPicture);
}
@@ -7330,7 +7331,7 @@
extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
}
- mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE);
+ mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
@@ -9972,6 +9973,15 @@
* <p>The shortcut activity will be used when the bubble is expanded. This will display
* the shortcut activity in a floating window over the existing foreground activity.</p>
*
+ * <p>When the shortcut is displayed in a bubble, there will be an intent
+ * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+ * with {@code true}. You may check this in the onCreate of your activity via:
+ *
+ * <pre class="prettyprint">
+ * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+ * </pre>
+ * </p>
+ *
* <p>If the shortcut has not been published when the bubble notification is sent,
* no bubble will be produced. If the shortcut is deleted while the bubble is active,
* the bubble will be removed.</p>
@@ -10000,6 +10010,15 @@
* app content in a floating window over the existing foreground activity. The intent
* should point to a resizable activity. </p>
*
+ * <p>When the activity is displayed in a bubble, there will be an intent
+ * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+ * with {@code true}. You may check this in the onCreate of your activity via:
+ *
+ * <pre class="prettyprint">
+ * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+ * </pre>
+ * </p>
+ *
* @throws NullPointerException if intent is null.
* @throws NullPointerException if icon is null.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7404e53..d5e9570 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -167,9 +167,11 @@
import android.os.SystemConfigManager;
import android.os.SystemUpdateManager;
import android.os.SystemVibrator;
+import android.os.SystemVibratorManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibratorManager;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
@@ -699,6 +701,13 @@
}
});
+ registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
+ new CachedServiceFetcher<VibratorManager>() {
+ @Override
+ public VibratorManager createService(ContextImpl ctx) {
+ return new SystemVibratorManager(ctx);
+ }});
+
registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
new CachedServiceFetcher<Vibrator>() {
@Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 390d921..938ce0d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -53,13 +53,6 @@
public int userId;
/**
- * The id of the ActivityStack that currently contains this task.
- * @hide
- */
- @UnsupportedAppUsage
- public int stackId;
-
- /**
* The identifier for this task.
*/
public int taskId;
@@ -358,7 +351,6 @@
*/
void readFromParcel(Parcel source) {
userId = source.readInt();
- stackId = source.readInt();
taskId = source.readInt();
displayId = source.readInt();
isRunning = source.readBoolean();
@@ -394,7 +386,6 @@
*/
void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
- dest.writeInt(stackId);
dest.writeInt(taskId);
dest.writeInt(displayId);
dest.writeBoolean(isRunning);
@@ -428,7 +419,7 @@
@Override
public String toString() {
- return "TaskInfo{userId=" + userId + " stackId=" + stackId + " taskId=" + taskId
+ return "TaskInfo{userId=" + userId + " taskId=" + taskId
+ " displayId=" + displayId
+ " isRunning=" + isRunning
+ " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d175a66..4dbff0c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -16,6 +16,8 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.OperationSafetyReason;
+
import android.accounts.AccountManager;
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
@@ -35,6 +37,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.security.KeyChain;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -72,8 +75,8 @@
* </div>
*/
public class DeviceAdminReceiver extends BroadcastReceiver {
- private static String TAG = "DevicePolicy";
- private static boolean localLOGV = false;
+ private static final String TAG = "DevicePolicy";
+ private static final boolean LOCAL_LOGV = false;
/**
* This is the primary action that a device administrator must implement to be
@@ -509,6 +512,36 @@
public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
"android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
+ /**
+ * Broadcast action: notify the admin that the state of operations that can be unsafe because
+ * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra)
+ * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE}
+ * {@code boolean} extra).
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED =
+ "android.app.action.OPERATION_SAFETY_STATE_CHANGED";
+
+ /**
+ * An {@code int} extra specifying an {@link OperationSafetyReason}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_OPERATION_SAFETY_REASON =
+ "android.app.extra.OPERATION_SAFETY_REASON";
+
+ /**
+ * An {@code boolean} extra specifying whether an operation will fail due to a
+ * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are
+ * safe, while {@code false} means they're unsafe.
+ *
+ * @hide
+ */
+ public static final String EXTRA_OPERATION_SAFETY_STATE =
+ "android.app.extra.OPERATION_SAFETY_STATE";
+
private DevicePolicyManager mManager;
private ComponentName mWho;
@@ -1018,6 +1051,51 @@
}
/**
+ * Called to notify the state of operations that can be unsafe to execute has changed.
+ *
+ * <p><b>Note:/b> notice that the operation safety state might change between the time this
+ * callback is received and the operation's method on {@link DevicePolicyManager} is called, so
+ * calls to the latter could still throw a {@link UnsafeStateException} even when this method
+ * is called with {@code isSafe} as {@code true}
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param reason the reason an operation could be unsafe.
+ * @param isSafe whether the operation is safe to be executed.
+ */
+ public void onOperationSafetyStateChanged(@NonNull Context context,
+ @OperationSafetyReason int reason, boolean isSafe) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b",
+ DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+ }
+ }
+
+ private void onOperationSafetyStateChanged(Context context, Intent intent) {
+ if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON)
+ || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) {
+ return;
+ }
+
+ int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON,
+ DevicePolicyManager.OPERATION_SAFETY_REASON_NONE);
+ if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) {
+ Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason);
+ return;
+ }
+ boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE,
+ /* defaultValue=*/ false);
+
+ onOperationSafetyStateChanged(context, reason, isSafe);
+ }
+
+ private boolean hasRequiredExtra(Intent intent, String extra) {
+ if (intent.hasExtra(extra)) return true;
+
+ Log.wtf(TAG, "Missing '" + extra + "' on intent " + intent);
+ return false;
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -1025,6 +1103,9 @@
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId());
+ }
if (ACTION_PASSWORD_CHANGED.equals(action)) {
onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
@@ -1092,6 +1173,8 @@
} else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
onTransferAffiliatedProfileOwnershipComplete(context,
intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
+ onOperationSafetyStateChanged(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff41d1c8..ff6f6a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -206,7 +206,6 @@
* {@link android.os.Build.VERSION_CODES#N}</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
@@ -250,7 +249,6 @@
* <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* </ul>
*
* <p>If provisioning fails, the device returns to its previous state.
@@ -289,7 +287,6 @@
* <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li>
* </ul>
@@ -388,8 +385,6 @@
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
* <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
@@ -438,8 +433,6 @@
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
@@ -654,7 +647,10 @@
*
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or
* {@link #ACTION_PROVISION_MANAGED_DEVICE}.
+ *
+ * @deprecated Color customization is no longer supported in the provisioning flow.
*/
+ @Deprecated
public static final String EXTRA_PROVISIONING_MAIN_COLOR =
"android.app.extra.PROVISIONING_MAIN_COLOR";
@@ -905,8 +901,10 @@
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
* or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
+ * @deprecated This extra is no longer respected in the provisioning flow.
* @hide
*/
+ @Deprecated
@SystemApi
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL =
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -930,9 +928,11 @@
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
* or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
+ * @deprecated This extra is no longer respected in the provisioning flow.
* @hide
*/
@SystemApi
+ @Deprecated
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI =
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
@@ -2922,33 +2922,61 @@
return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
}
- private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+ private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_";
/** @hide */
- @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
- UNSAFE_OPERATION_REASON_NONE,
- UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+ @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = {
+ OPERATION_SAFETY_REASON_NONE,
+ OPERATION_SAFETY_REASON_DRIVING_DISTRACTION
})
@Retention(RetentionPolicy.SOURCE)
- public static @interface UnsafeOperationReason {
+ public static @interface OperationSafetyReason {
}
/** @hide */
@TestApi
- public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+ public static final int OPERATION_SAFETY_REASON_NONE = -1;
/**
* Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
* the driver of the vehicle.
*/
- public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+ public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
/** @hide */
@NonNull
@TestApi
- public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+ public static String operationSafetyReasonToString(@OperationSafetyReason int reason) {
return DebugUtils.constantToString(DevicePolicyManager.class,
- PREFIX_UNSAFE_OPERATION_REASON, reason);
+ PREFIX_OPERATION_SAFETY_REASON, reason);
+ }
+
+ /** @hide */
+ public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) {
+ return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+ }
+
+ /**
+ * Checks if it's safe to run operations that can be affected by the given {@code reason}.
+ *
+ * <p><b>Note:/b> notice that the operation safety state might change between the time this
+ * method returns and the operation's method is called, so calls to the latter could still throw
+ * a {@link UnsafeStateException} even when this method returns {@code true}.
+ *
+ * @param reason currently, only supported reason is
+ * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
+ *
+ * @return whether it's safe to run operations that can be affected by the given {@code reason}.
+ */
+ // TODO(b/173541467): should it throw SecurityException if caller is not admin?
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ if (mService == null) return false;
+
+ try {
+ return mService.isSafeOperation(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** @hide */
@@ -13157,7 +13185,7 @@
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
public void setNextOperationSafety(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
if (mService != null) {
try {
mService.setNextOperationSafety(operation, reason);
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index a0d2977..67f5c36 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.content.ComponentName;
import android.content.Intent;
import android.os.UserHandle;
@@ -255,4 +256,14 @@
* {@link #supportsResetOp(int)} is true.
*/
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+ /**
+ * Notifies the system that an unsafe operation reason has changed.
+ *
+ * @throws IllegalArgumentException if {@code checker} is not the same as set on
+ * {@code DevicePolicyManagerService}.
+ */
+ public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
+ @OperationSafetyReason int reason, boolean isSafe);
+
}
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index 6c6f2aa..17b74b1 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,7 +17,7 @@
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import com.android.internal.os.IResultReceiver;
@@ -31,15 +31,20 @@
/**
* Returns whether the given {@code operation} can be safely executed at the moment.
*/
- @UnsafeOperationReason
+ @OperationSafetyReason
int getUnsafeOperationReason(@DevicePolicyOperation int operation);
/**
+ * Return whether it's safe to run operations that can be affected by the given {@code reason}.
+ */
+ boolean isSafeOperation(@OperationSafetyReason int reason);
+
+ /**
* Returns a new exception for when the given {@code operation} cannot be safely executed.
*/
@NonNull
default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
return new UnsafeStateException(operation, reason);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 89f30cc..032cf24 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -492,6 +492,7 @@
boolean canProfileOwnerResetPasswordWhenLocked(int userId);
void setNextOperationSafety(int operation, int reason);
+ boolean isSafeOperation(int reason);
String getEnrollmentSpecificId(String callerPackage);
void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 56eeb06..f1f6526 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,18 +15,20 @@
*/
package android.app.admin;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
* was not safe to be executed at that moment.
@@ -39,17 +41,15 @@
public final class UnsafeStateException extends IllegalStateException implements Parcelable {
private final @DevicePolicyOperation int mOperation;
- private final @UnsafeOperationReason int mReason;
+ private final @OperationSafetyReason int mReason;
/** @hide */
@TestApi
public UnsafeStateException(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
super();
- Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
- "invalid reason %d, must be %d (%s)", reason,
- UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
- unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
+ Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d",
+ reason);
mOperation = operation;
mReason = reason;
}
@@ -61,19 +61,20 @@
}
/**
- * Gets the reason the operation is unsafe.
+ * Gets the reasons the operation is unsafe.
*
* @return currently, only valid reason is
- * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+ * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
*/
- public @UnsafeOperationReason int getReason() {
- return mReason;
+ @NonNull
+ public List<Integer> getReasons() {
+ return Arrays.asList(mReason);
}
/** @hide */
@Override
public String getMessage() {
- return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+ return DevicePolicyManager.operationSafetyReasonToString(mReason);
}
@Override
diff --git a/core/java/android/app/compat/OWNERS b/core/java/android/app/compat/OWNERS
new file mode 100644
index 0000000..f8c3520
--- /dev/null
+++ b/core/java/android/app/compat/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/compat/OWNERS
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
rename to core/java/android/app/time/ExternalTimeSuggestion.aidl
index 14d57bf..07a0fbb 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
parcelable ExternalTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
similarity index 99%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.java
rename to core/java/android/app/time/ExternalTimeSuggestion.java
index 7ad303a..b566eab 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 4626543..c4546be 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,7 +16,7 @@
package android.app.timedetector;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index df8d797..76f3785 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.app.time.ExternalTimeSuggestion;
import android.content.Context;
import android.os.SystemClock;
import android.os.TimestampedValue;
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index f80869f..ef818ef 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,6 +17,7 @@
package android.app.timedetector;
import android.annotation.NonNull;
+import android.app.time.ExternalTimeSuggestion;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index d7dc86a..46d8900 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -63,7 +63,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.system.Int32Ref;
+import android.system.Int64Ref;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -4050,7 +4050,7 @@
// Convert to Point, since that's what the API is defined as
final Bundle opts = new Bundle();
opts.putParcelable(EXTRA_SIZE, new Point(size.getWidth(), size.getHeight()));
- final Int32Ref orientation = new Int32Ref(0);
+ final Int64Ref orientation = new Int64Ref(0);
Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a402b2..025d777 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3479,6 +3479,7 @@
STORAGE_STATS_SERVICE,
WALLPAPER_SERVICE,
TIME_ZONE_RULES_MANAGER_SERVICE,
+ VIBRATOR_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
@@ -3625,9 +3626,11 @@
* (e.g., GPS) updates.
* <dt> {@link #SEARCH_SERVICE} ("search")
* <dd> A {@link android.app.SearchManager} for handling search.
+ * <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+ * <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+ * with individual ones and playing synchronized effects on multiple vibrators.
* <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
- * <dd> A {@link android.os.Vibrator} for interacting with the vibrator
- * hardware.
+ * <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
* <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
* <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
* handling management of network connections.
@@ -3707,6 +3710,8 @@
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
* @see android.os.storage.StorageManager
+ * @see #VIBRATOR_MANAGER_SERVICE
+ * @see android.os.VibratorManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
@@ -4034,8 +4039,19 @@
public static final String WALLPAPER_SERVICE = "wallpaper";
/**
- * Use with {@link #getSystemService(String)} to retrieve a {@link
- * android.os.Vibrator} for interacting with the vibration hardware.
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+ * for accessing the device vibrators, interacting with individual ones and playing synchronized
+ * effects on multiple vibrators.
+ *
+ * @see #getSystemService(String)
+ * @see android.os.VibratorManager
+ */
+ @SuppressLint("ServiceName")
+ public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+ * interacting with the vibration hardware.
*
* @see #getSystemService(String)
* @see android.os.Vibrator
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30b2404..4abd8cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5718,7 +5718,7 @@
public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
/**
- * Used in the extra field in the remote intent. It's astring token passed with the
+ * Used in the extra field in the remote intent. It's a string token passed with the
* remote intent.
*/
public static final String EXTRA_REMOTE_INTENT_TOKEN =
@@ -6062,6 +6062,16 @@
*/
public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
+ /**
+ * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or
+ * pending intent provided for the bubble. If the extra is not present or false, then it is not
+ * bubbled.
+ *
+ * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+ * @see android.app.Notification.BubbleMetadata.Builder#Builder(String)
+ */
+ public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/om/CriticalOverlayInfo.java b/core/java/android/content/om/CriticalOverlayInfo.java
new file mode 100644
index 0000000..8fbc698
--- /dev/null
+++ b/core/java/android/content/om/CriticalOverlayInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * A subset of {@link OverlayInfo} fields that when changed cause the overlay's settings to be
+ * completely reinitialized.
+ *
+ * @hide
+ */
+public interface CriticalOverlayInfo {
+
+ /**
+ * @return the package name of the overlay.
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * @return the unique name of the overlay within its containing package.
+ */
+ @Nullable
+ String getOverlayName();
+
+ /**
+ * @return the target package name of the overlay.
+ */
+ @NonNull
+ String getTargetPackageName();
+
+ /**
+ * @return the name of the target overlayable declaration.
+ */
+ @Nullable
+ String getTargetOverlayableName();
+
+ /**
+ * @return an identifier representing the current overlay.
+ */
+ @NonNull
+ OverlayIdentifier getOverlayIdentifier();
+
+ /**
+ * Returns whether or not the overlay is a {@link FabricatedOverlay}.
+ */
+ boolean isFabricated();
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
new file mode 100644
index 0000000..d62b47b
--- /dev/null
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -0,0 +1,126 @@
+/*
+ * 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.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ *
+ * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+ * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+ * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
+ * system partition would fulfil the {@code system} and {@code signature} policies.
+ *
+ * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+ * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ *
+ * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
+ * overlays and fabricated overlays.
+ * @hide
+ */
+public class FabricatedOverlay {
+
+ /** Retrieves the identifier for this fabricated overlay. */
+ public OverlayIdentifier getIdentifier() {
+ return new OverlayIdentifier(
+ mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
+ }
+
+ public static class Builder {
+ private final String mOwningPackage;
+ private final String mName;
+ private final String mTargetPackage;
+ private String mTargetOverlayable = "";
+ private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
+
+ /**
+ * Constructs a build for a fabricated overlay.
+ *
+ * @param owningPackage the name of the package that owns the fabricated overlay (must
+ * be a package name of this UID).
+ * @param name a name used to uniquely identify the fabricated overlay owned by
+ * {@param owningPackageName}
+ * @param targetPackage the name of the package to overlay
+ */
+ public Builder(@NonNull String owningPackage, @NonNull String name,
+ @NonNull String targetPackage) {
+ Preconditions.checkStringNotEmpty(owningPackage,
+ "'owningPackage' must not be empty nor null");
+ Preconditions.checkStringNotEmpty(name,
+ "'name'' must not be empty nor null");
+ Preconditions.checkStringNotEmpty(targetPackage,
+ "'targetPackage' must not be empty nor null");
+
+ mOwningPackage = owningPackage;
+ mName = name;
+ mTargetPackage = targetPackage;
+ }
+
+ /**
+ * Sets the name of the overlayable resources to overlay (can be null).
+ */
+ public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
+ mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+ return this;
+ }
+
+ /**
+ * Sets the value of
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the unsigned 32 bit integer representing the new value
+ *
+ * @see android.util.TypedValue#type
+ */
+ public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = dataType;
+ entry.data = value;
+ mEntries.add(entry);
+ return this;
+ }
+
+ /** Builds an immutable fabricated overlay. */
+ public FabricatedOverlay build() {
+ final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+ overlay.packageName = mOwningPackage;
+ overlay.overlayName = mName;
+ overlay.targetPackageName = mTargetPackage;
+ overlay.targetOverlayable = mTargetOverlayable;
+ overlay.entries = new ArrayList<>();
+ overlay.entries.addAll(mEntries);
+ return new FabricatedOverlay(overlay);
+ }
+ }
+
+ final FabricatedOverlayInternal mOverlay;
+ private FabricatedOverlay(FabricatedOverlayInternal overlay) {
+ mOverlay = overlay;
+ }
+}
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 0b950b4..e319d2c 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -16,6 +16,7 @@
package android.content.om;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
@@ -66,6 +67,17 @@
OverlayInfo getOverlayInfo(in String packageName, in int userId);
/**
+ * Returns information about the overlay with the given package name for the
+ * specified user.
+ *
+ * @param packageName The name of the overlay package.
+ * @param userId The user to get the OverlayInfo for.
+ * @return The OverlayInfo for the overlay package; or null if no such
+ * overlay package exists.
+ */
+ OverlayInfo getOverlayInfoByIdentifier(in OverlayIdentifier packageName, in int userId);
+
+ /**
* Request that an overlay package be enabled or disabled when possible to
* do so.
*
@@ -163,7 +175,7 @@
* Invalidates and removes the idmap for an overlay,
* @param packageName The name of the overlay package whose idmap should be deleted.
*/
- void invalidateCachesForOverlay(in String packageName, in int userIs);
+ void invalidateCachesForOverlay(in String packageName, in int userId);
/**
* Perform a series of requests related to overlay packages. This is an
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/content/om/OverlayIdentifier.aidl
similarity index 89%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/content/om/OverlayIdentifier.aidl
index 14d57bf..d1c7770 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/content/om/OverlayIdentifier.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.content.om;
-parcelable ExternalTimeSuggestion;
+parcelable OverlayIdentifier;
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
new file mode 100644
index 0000000..454d0d1
--- /dev/null
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -0,0 +1,208 @@
+/*
+ * 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.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ *
+ * An overlay always belongs to a package and may optionally have a name associated with it.
+ * The name helps uniquely identify a particular overlay within a package.
+ * @hide
+ */
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
+ genEqualsHashCode = true, genToString = false)
+public class OverlayIdentifier implements Parcelable {
+ /**
+ * The package name containing or owning the overlay.
+ */
+ @Nullable
+ private final String mPackageName;
+
+ /**
+ * The unique name within the package of the overlay.
+ */
+ @Nullable
+ private final String mOverlayName;
+
+ /**
+ * Creates an identifier from a package and unique name within the package.
+ *
+ * @param packageName the package containing or owning the overlay
+ * @param overlayName the unique name of the overlay within the package
+ */
+ public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
+ mPackageName = packageName;
+ mOverlayName = overlayName;
+ }
+
+ /**
+ * Creates an identifier for an overlay without a name.
+ *
+ * @param packageName the package containing or owning the overlay
+ */
+ public OverlayIdentifier(@NonNull String packageName) {
+ mPackageName = packageName;
+ mOverlayName = null;
+ }
+
+ @Override
+ public String toString() {
+ return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
+ }
+
+ /** @hide */
+ public static OverlayIdentifier fromString(@NonNull String text) {
+ final String[] parts = text.split(":", 2);
+ if (parts.length == 2) {
+ return new OverlayIdentifier(parts[0], parts[1]);
+ } else {
+ return new OverlayIdentifier(parts[0]);
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayIdentifier.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Retrieves the package name containing or owning the overlay.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Retrieves the unique name within the package of the overlay.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getOverlayName() {
+ return mOverlayName;
+ }
+
+ @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(OverlayIdentifier other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ OverlayIdentifier that = (OverlayIdentifier) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mOverlayName, that.mOverlayName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Objects.hashCode(mOverlayName);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mPackageName != null) flg |= 0x1;
+ if (mOverlayName != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mPackageName != null) dest.writeString(mPackageName);
+ if (mOverlayName != null) dest.writeString(mOverlayName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected OverlayIdentifier(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String packageName = (flg & 0x1) == 0 ? null : in.readString();
+ String overlayName = (flg & 0x2) == 0 ? null : in.readString();
+
+ this.mPackageName = packageName;
+ this.mOverlayName = overlayName;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OverlayIdentifier> CREATOR
+ = new Parcelable.Creator<OverlayIdentifier>() {
+ @Override
+ public OverlayIdentifier[] newArray(int size) {
+ return new OverlayIdentifier[size];
+ }
+
+ @Override
+ public OverlayIdentifier createFromParcel(@NonNull Parcel in) {
+ return new OverlayIdentifier(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1612482438728L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 517e4bd..c66f49c 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -26,6 +26,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -37,7 +39,7 @@
* @hide
*/
@SystemApi
-public final class OverlayInfo implements Parcelable {
+public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
/** @hide */
@IntDef(prefix = "STATE_", value = {
@@ -143,6 +145,14 @@
public final String packageName;
/**
+ * The unique name within the package of the overlay.
+ *
+ * @hide
+ */
+ @Nullable
+ public final String overlayName;
+
+ /**
* Package name of the target package
*
* @hide
@@ -201,6 +211,14 @@
*/
public final boolean isMutable;
+ private OverlayIdentifier mIdentifierCached;
+
+ /**
+ *
+ * @hide
+ */
+ public final boolean isFabricated;
+
/**
* Create a new OverlayInfo based on source with an updated state.
*
@@ -210,17 +228,28 @@
* @hide
*/
public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
- this(source.packageName, source.targetPackageName, source.targetOverlayableName,
- source.category, source.baseCodePath, state, source.userId, source.priority,
- source.isMutable);
+ this(source.packageName, source.overlayName, source.targetPackageName,
+ source.targetOverlayableName, source.category, source.baseCodePath, state,
+ source.userId, source.priority, source.isMutable, source.isFabricated);
}
/** @hide */
+ @VisibleForTesting
public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
@Nullable String targetOverlayableName, @Nullable String category,
- @NonNull String baseCodePath, int state, int userId,
- int priority, boolean isMutable) {
+ @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
+ this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
+ category, baseCodePath, state, userId, priority, isMutable,
+ false /* isFabricated */);
+ }
+
+ /** @hide */
+ public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
+ @NonNull String targetPackageName, @Nullable String targetOverlayableName,
+ @Nullable String category, @NonNull String baseCodePath, int state, int userId,
+ int priority, boolean isMutable, boolean isFabricated) {
this.packageName = packageName;
+ this.overlayName = overlayName;
this.targetPackageName = targetPackageName;
this.targetOverlayableName = targetOverlayableName;
this.category = category;
@@ -229,12 +258,14 @@
this.userId = userId;
this.priority = priority;
this.isMutable = isMutable;
+ this.isFabricated = isFabricated;
ensureValidState();
}
/** @hide */
public OverlayInfo(Parcel source) {
packageName = source.readString();
+ overlayName = source.readString();
targetPackageName = source.readString();
targetOverlayableName = source.readString();
category = source.readString();
@@ -243,13 +274,15 @@
userId = source.readInt();
priority = source.readInt();
isMutable = source.readBoolean();
+ isFabricated = source.readBoolean();
ensureValidState();
}
/**
- * Returns package name of the current overlay.
+ * {@inheritDoc}
* @hide
*/
+ @Override
@SystemApi
@NonNull
public String getPackageName() {
@@ -257,9 +290,20 @@
}
/**
- * Returns the target package name of the current overlay.
+ * {@inheritDoc}
* @hide
*/
+ @Override
+ @Nullable
+ public String getOverlayName() {
+ return overlayName;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
@SystemApi
@NonNull
public String getTargetPackageName() {
@@ -268,7 +312,8 @@
/**
* Returns the category of the current overlay.
- * @hide\
+ *
+ * @hide
*/
@SystemApi
@Nullable
@@ -278,6 +323,7 @@
/**
* Returns user handle for which this overlay applies to.
+ *
* @hide
*/
@SystemApi
@@ -287,15 +333,47 @@
}
/**
- * Returns name of the target overlayable declaration.
+ * {@inheritDoc}
* @hide
*/
+ @Override
@SystemApi
@Nullable
public String getTargetOverlayableName() {
return targetOverlayableName;
}
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ public boolean isFabricated() {
+ return isFabricated;
+ }
+
+ /**
+ * Full path to the base APK or fabricated overlay for this overlay package.
+ *
+ * @hide
+ */
+ public String getBaseCodePath() {
+ return baseCodePath;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @NonNull
+ public OverlayIdentifier getOverlayIdentifier() {
+ if (mIdentifierCached == null) {
+ mIdentifierCached = new OverlayIdentifier(packageName, overlayName);
+ }
+ return mIdentifierCached;
+ }
+
@SuppressWarnings("ConstantConditions")
private void ensureValidState() {
if (packageName == null) {
@@ -330,6 +408,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
+ dest.writeString(overlayName);
dest.writeString(targetPackageName);
dest.writeString(targetOverlayableName);
dest.writeString(category);
@@ -338,6 +417,7 @@
dest.writeInt(userId);
dest.writeInt(priority);
dest.writeBoolean(isMutable);
+ dest.writeBoolean(isFabricated);
}
public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
@@ -410,6 +490,7 @@
result = prime * result + userId;
result = prime * result + state;
result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
+ result = prime * result + ((overlayName == null) ? 0 : overlayName.hashCode());
result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
result = prime * result + ((targetOverlayableName == null) ? 0
: targetOverlayableName.hashCode());
@@ -439,6 +520,9 @@
if (!packageName.equals(other.packageName)) {
return false;
}
+ if (!Objects.equals(overlayName, other.overlayName)) {
+ return false;
+ }
if (!targetPackageName.equals(other.targetPackageName)) {
return false;
}
@@ -457,9 +541,13 @@
@NonNull
@Override
public String toString() {
- return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
- + ((targetOverlayableName == null) ? ""
- : ", targetOverlayable=" + targetOverlayableName)
- + ", state=" + state + " (" + stateToString(state) + "), userId=" + userId + " }";
+ return "OverlayInfo {"
+ + "packageName=" + packageName
+ + ", overlayName=" + overlayName
+ + ", targetPackage=" + targetPackageName
+ + ", targetOverlayable=" + targetOverlayableName
+ + ", state=" + state + " (" + stateToString(state) + "),"
+ + ", userId=" + userId
+ + " }";
}
}
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7c14c28..0f7e01b 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -205,6 +205,25 @@
}
/**
+ * Returns information about the overlay represented by the identifier for the specified user.
+ *
+ * @param overlay the identifier representing the overlay
+ * @param userHandle the user of which to get overlay state info
+ * @return the overlay info or null if the overlay cannot be found
+ *
+ * @hide
+ */
+ @Nullable
+ public OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay,
+ @NonNull final UserHandle userHandle) {
+ try {
+ return mService.getOverlayInfoByIdentifier(overlay, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns information about all overlays for the given target package for
* the specified user. The returned list is ordered according to the
* overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 1fa8973..73be0ff 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,6 +20,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -29,6 +32,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
/**
* Container for a batch of requests to the OverlayManagerService.
@@ -60,12 +64,13 @@
private OverlayManagerTransaction(@NonNull final Parcel source) {
final int size = source.readInt();
- mRequests = new ArrayList<Request>(size);
+ mRequests = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final int request = source.readInt();
- final String packageName = source.readString();
+ final OverlayIdentifier overlay = source.readParcelable(null);
final int userId = source.readInt();
- mRequests.add(new Request(request, packageName, userId));
+ final Bundle extras = source.readBundle(null);
+ mRequests.add(new Request(request, overlay, userId, extras));
}
}
@@ -95,22 +100,36 @@
public static final int TYPE_SET_ENABLED = 0;
public static final int TYPE_SET_DISABLED = 1;
+ public static final int TYPE_REGISTER_FABRICATED = 2;
+ public static final int TYPE_UNREGISTER_FABRICATED = 3;
- @RequestType public final int type;
- public final String packageName;
+ public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
+
+ @RequestType
+ public final int type;
+ @NonNull
+ public final OverlayIdentifier overlay;
public final int userId;
+ @Nullable
+ public final Bundle extras;
- public Request(@RequestType final int type, @NonNull final String packageName,
+ public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
final int userId) {
+ this(type, overlay, userId, null /* extras */);
+ }
+
+ public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+ final int userId, @Nullable Bundle extras) {
this.type = type;
- this.packageName = packageName;
+ this.overlay = overlay;
this.userId = userId;
+ this.extras = extras;
}
@Override
public String toString() {
- return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}",
- type, typeToString(), packageName, userId);
+ return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
+ type, typeToString(), overlay, userId);
}
/**
@@ -123,6 +142,8 @@
switch (type) {
case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+ case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
+ case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
}
}
@@ -152,22 +173,56 @@
* longer affect the resources of the target package. If the target is
* currently running, its outdated resources will be replaced by new ones.
*
- * @param packageName The name of the overlay package.
+ * @param overlay The name of the overlay package.
* @param enable true to enable the overlay, false to disable it.
* @return this Builder object, so you can chain additional requests
*/
- public Builder setEnabled(@NonNull String packageName, boolean enable) {
- return setEnabled(packageName, enable, UserHandle.myUserId());
+ public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
+ return setEnabled(overlay, enable, UserHandle.myUserId());
}
/**
* @hide
*/
- public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) {
- checkNotNull(packageName);
+ public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
+ checkNotNull(overlay);
@Request.RequestType final int type =
enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
- mRequests.add(new Request(type, packageName, userId));
+ mRequests.add(new Request(type, overlay, userId));
+ return this;
+ }
+
+ /**
+ * Registers the fabricated overlay with the overlay manager so it can be enabled and
+ * disabled for any user.
+ *
+ * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
+ * the existing overlay will be replaced by the newly registered overlay and the enabled
+ * state of the overlay will be left unchanged if the target package and target overlayable
+ * have not changed.
+ *
+ * @param overlay the overlay to register with the overlay manager
+ *
+ * @hide
+ */
+ public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+ final Bundle extras = new Bundle();
+ extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+ mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+ UserHandle.USER_ALL, extras));
+ return this;
+ }
+
+ /**
+ * Disables and removes the overlay from the overlay manager for all users.
+ *
+ * @param overlay the overlay to disable and remove
+ *
+ * @hide
+ */
+ public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+ mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
+ UserHandle.USER_ALL));
return this;
}
@@ -195,8 +250,9 @@
for (int i = 0; i < size; i++) {
final Request req = mRequests.get(i);
dest.writeInt(req.type);
- dest.writeString(req.packageName);
+ dest.writeParcelable(req.overlay, flags);
dest.writeInt(req.userId);
+ dest.writeBundle(req.extras);
}
}
diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java
index e8fb241..4d79936 100644
--- a/core/java/android/content/pm/DataLoaderManager.java
+++ b/core/java/android/content/pm/DataLoaderManager.java
@@ -41,6 +41,7 @@
* @param dataLoaderId ID for the new data loader binder service.
* @param params DataLoaderParamsParcel object that contains data loader params, including
* its package name, class name, and additional parameters.
+ * @param bindDelayMs introduce a delay before actual bind in case we want to avoid busylooping
* @param listener Callback for the data loader service to report status back to the
* caller.
* @return false if 1) target ID collides with a data loader that is already bound to data
@@ -48,9 +49,9 @@
* or 4) fails to bind to the specified data loader service, otherwise return true.
*/
public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
- @NonNull IDataLoaderStatusListener listener) {
+ long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
try {
- return mService.bindToDataLoader(dataLoaderId, params, listener);
+ return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl
index 93b3de7..dda4d36 100644
--- a/core/java/android/content/pm/IDataLoaderManager.aidl
+++ b/core/java/android/content/pm/IDataLoaderManager.aidl
@@ -23,7 +23,7 @@
/** @hide */
interface IDataLoaderManager {
- boolean bindToDataLoader(int id, in DataLoaderParamsParcel params,
+ boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs,
IDataLoaderStatusListener listener);
IDataLoader getDataLoader(int dataLoaderId);
void unbindFromDataLoader(int dataLoaderId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a46876e..7fe2a41 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -808,4 +808,6 @@
PackageManager.Property getProperty(String propertyName, String packageName, String className);
ParceledListSlice queryProperty(String propertyName, int componentType);
+
+ void setKeepUninstalledPackages(in List<String> packageList);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b95b991b..a3c3500 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -3592,9 +3592,12 @@
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
* the requisite kernel support to support incremental delivery aka Incremental FileSystem.
*
- * @see IncrementalManager#isEnabled
+ * @see IncrementalManager#isFeatureEnabled
* @hide
+ *
+ * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead.
*/
+ @Deprecated
@SystemApi
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_INCREMENTAL_DELIVERY =
@@ -3602,6 +3605,20 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * feature not present - IncFs is not present on the device.
+ * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+ * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+ *
+ * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2()
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION =
+ "android.software.incremental_delivery_version";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device has tuner hardware to support tuner operations.
*
* <p>This feature implies that the device has the tuner HAL implementation.
@@ -9282,4 +9299,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Set a list of apps to keep around as APKs even if no user has currently installed it.
+ * @param packageList List of package names to keep cached.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+ @TestApi
+ public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+ try {
+ ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d17..bf8d1f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6151,6 +6151,56 @@
}
/**
+ * Returns whether this instance is currently signed, or has ever been signed, with a
+ * signing certificate from the provided {@link Set} of {@code certDigests}.
+ *
+ * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+ * of each trusted certificate with the digest characters in upper case. If this instance
+ * has multiple signers then all signers must be in the provided {@code Set}. If this
+ * instance has a signing lineage then this method will return true if any of the previous
+ * signers in the lineage match one of the entries in the {@code Set}.
+ */
+ public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+ if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+ return false;
+ }
+ // If an app is signed by multiple signers then all of the signers must be in the Set.
+ if (signatures.length > 1) {
+ // If the Set has less elements than the number of signatures then immediately
+ // return false as there's no way to satisfy the requirement of all signatures being
+ // in the Set.
+ if (certDigests.size() < signatures.length) {
+ return false;
+ }
+ for (Signature signature : signatures) {
+ String signatureDigest = PackageUtils.computeSha256Digest(
+ signature.toByteArray());
+ if (!certDigests.contains(signatureDigest)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ if (hasPastSigningCertificates()) {
+ // The last element in the pastSigningCertificates array is the current signer;
+ // since that was verified above just check all the signers in the lineage.
+ for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+ signatureDigest = PackageUtils.computeSha256Digest(
+ pastSigningCertificates[i].toByteArray());
+ if (certDigests.contains(signatureDigest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the SigningDetails with a descendant (or same) signer after verifying the
* descendant has the same, a superset, or a subset of the lineage of the ancestor.
*
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8..0e70a3e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -28,8 +28,12 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
/**
* Information you can retrieve about a particular security permission
@@ -278,6 +282,15 @@
@SystemApi
public static final int PROTECTION_FLAG_ROLE = 0x4000000;
+ /**
+ * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+ * of {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -303,6 +316,7 @@
PROTECTION_FLAG_RETAIL_DEMO,
PROTECTION_FLAG_RECENTS,
PROTECTION_FLAG_ROLE,
+ PROTECTION_FLAG_KNOWN_SIGNER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -466,6 +480,17 @@
*/
public @Nullable CharSequence nonLocalizedDescription;
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+ /**
+ * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+ * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+ * if the app is signed by any of these certificates.
+ *
+ * @hide
+ */
+ public @Nullable Set<String> knownCerts;
+
/** @hide */
public static int fixProtectionLevel(int level) {
if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -570,6 +595,9 @@
if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
protLevel.append("|role");
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+ protLevel.append("|knownSigner");
+ }
return protLevel.toString();
}
@@ -665,6 +693,7 @@
dest.writeInt(descriptionRes);
dest.writeInt(requestRes);
TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+ sForStringSet.parcel(knownCerts, dest, parcelableFlags);
}
/** @hide */
@@ -730,5 +759,6 @@
descriptionRes = source.readInt();
requestRes = source.readInt();
nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ knownCerts = sForStringSet.unparcel(source);
}
}
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904..9a84ded 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -655,6 +655,7 @@
pi.protectionLevel = p.getProtectionLevel();
pi.descriptionRes = p.getDescriptionRes();
pi.flags = p.getFlags();
+ pi.knownCerts = p.getKnownCerts();
if ((flags & PackageManager.GET_META_DATA) == 0) {
return pi;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 66bdb9b..b7aa30f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2807,7 +2807,7 @@
* limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
* @return Success if it's valid.
*/
- public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+ public static String validateName(String name, boolean requireSeparator,
boolean requireFilename) {
final int N = name.length();
boolean hasSep = false;
@@ -2828,18 +2828,28 @@
front = true;
continue;
}
- return input.error("bad character '" + c + "'");
+ return "bad character '" + c + "'";
}
if (requireFilename) {
if (!FileUtils.isValidExtFilename(name)) {
- return input.error("Invalid filename");
+ return "Invalid filename";
} else if (N > MAX_FILE_NAME_SIZE) {
- return input.error("the length of the name is greater than " + MAX_FILE_NAME_SIZE);
+ return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
}
}
- return hasSep || !requireSeparator
- ? input.success(null)
- : input.error("must have at least one '.' separator");
+ return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+ }
+
+ /**
+ * @see #validateName(String, boolean, boolean)
+ */
+ public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+ boolean requireFilename) {
+ final String errorMessage = validateName(name, requireSeparator, requireFilename);
+ if (errorMessage != null) {
+ return input.error(errorMessage);
+ }
+ return input.success(null);
}
/**
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1..37e0e87 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -21,14 +21,22 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.util.Locale;
+import java.util.Set;
/** @hide */
public class ParsedPermission extends ParsedComponent {
+ private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
@Nullable
String backgroundPermission;
@Nullable
@@ -39,6 +47,8 @@
boolean tree;
@Nullable
private ParsedPermissionGroup parsedPermissionGroup;
+ @Nullable
+ Set<String> knownCerts;
@VisibleForTesting
public ParsedPermission() {
@@ -81,6 +91,23 @@
return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
}
+ public @Nullable Set<String> getKnownCerts() {
+ return knownCerts;
+ }
+
+ protected void setKnownCert(String knownCert) {
+ // Convert the provided digest to upper case for consistent Set membership
+ // checks when verifying the signing certificate digests of requesting apps.
+ this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+ }
+
+ protected void setKnownCerts(String[] knownCerts) {
+ this.knownCerts = new ArraySet<>();
+ for (String knownCert : knownCerts) {
+ this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+ }
+ }
+
public int calculateFootprint() {
int size = getName().length();
if (getNonLocalizedLabel() != null) {
@@ -109,6 +136,7 @@
dest.writeInt(this.protectionLevel);
dest.writeBoolean(this.tree);
dest.writeParcelable(this.parsedPermissionGroup, flags);
+ sForStringSet.parcel(knownCerts, dest, flags);
}
protected ParsedPermission(Parcel in) {
@@ -121,6 +149,7 @@
this.protectionLevel = in.readInt();
this.tree = in.readBoolean();
this.parsedPermissionGroup = in.readParcelable(boot);
+ this.knownCerts = sForStringSet.unparcel(in);
}
public static final Parcelable.Creator<ParsedPermission> CREATOR =
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..8afa70e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -90,6 +90,38 @@
permission.flags = sa.getInt(
R.styleable.AndroidManifestPermission_permissionFlags, 0);
+ final int knownCertsResource = sa.getResourceId(
+ R.styleable.AndroidManifestPermission_knownCerts, 0);
+ if (knownCertsResource != 0) {
+ // The knownCerts attribute supports both a string array resource as well as a
+ // string resource for the case where the permission should only be granted to a
+ // single known signer.
+ final String resourceType = res.getResourceTypeName(knownCertsResource);
+ if (resourceType.equals("array")) {
+ final String[] knownCerts = res.getStringArray(knownCertsResource);
+ if (knownCerts != null) {
+ permission.setKnownCerts(knownCerts);
+ }
+ } else {
+ final String knownCert = res.getString(knownCertsResource);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+ if (permission.knownCerts == null) {
+ Slog.w(TAG, packageName + " defines a knownSigner permission but"
+ + " the provided knownCerts resource is null");
+ }
+ } else {
+ // If the knownCerts resource ID is null check if the app specified a string
+ // value for the attribute representing a single trusted signer.
+ final String knownCert = sa.getString(
+ R.styleable.AndroidManifestPermission_knownCerts);
+ if (knownCert != null) {
+ permission.setKnownCert(knownCert);
+ }
+ }
+
// For now only platform runtime permissions can be restricted
if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cde7b2a..d302b0a 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -3,7 +3,6 @@
toddke@android.com
toddke@google.com
patb@google.com
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
zhanghai@google.com
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index abf694f..bbde8b1 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -252,9 +252,10 @@
}
if (overrideScale != 1.0f) {
- applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
applicationScale = overrideScale;
applicationInvertedScale = 1.0f / overrideScale;
+ applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+ * applicationInvertedScale) + .5f);
compatFlags |= HAS_OVERRIDE_SCALING;
} else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
applicationDensity = DisplayMetrics.DENSITY_DEVICE;
@@ -519,10 +520,6 @@
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
- inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
- inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
- inoutConfig.smallestScreenWidthDp =
- (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index db047f8..f551d6a 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -16,18 +16,33 @@
package android.graphics.fonts;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.text.FontConfig;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Represents a font update request. Currently only font install request is supported.
* @hide
*/
-// TODO: Support font config update.
public final class FontUpdateRequest implements Parcelable {
+ public static final int TYPE_UPDATE_FONT_FILE = 0;
+ public static final int TYPE_UPDATE_FONT_FAMILY = 1;
+
+ @IntDef(prefix = "TYPE_", value = {
+ TYPE_UPDATE_FONT_FILE,
+ TYPE_UPDATE_FONT_FAMILY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
@Override
public FontUpdateRequest createFromParcel(Parcel in) {
@@ -40,39 +55,67 @@
}
};
- @NonNull
+ private final @Type int mType;
+ // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+ @Nullable
private final ParcelFileDescriptor mFd;
- @NonNull
+ // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+ @Nullable
private final byte[] mSignature;
+ // NonNull if mType == TYPE_UPDATE_FONT_FAMILY.
+ @Nullable
+ private final FontConfig.FontFamily mFontFamily;
public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
+ mType = TYPE_UPDATE_FONT_FILE;
mFd = fd;
mSignature = signature;
+ mFontFamily = null;
}
- private FontUpdateRequest(Parcel in) {
+ public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) {
+ mType = TYPE_UPDATE_FONT_FAMILY;
+ mFd = null;
+ mSignature = null;
+ mFontFamily = fontFamily;
+ }
+
+ protected FontUpdateRequest(Parcel in) {
+ mType = in.readInt();
mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
mSignature = in.readBlob();
+ mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
}
- @NonNull
+ public @Type int getType() {
+ return mType;
+ }
+
+ @Nullable
public ParcelFileDescriptor getFd() {
return mFd;
}
- @NonNull
+ @Nullable
public byte[] getSignature() {
return mSignature;
}
+ @Nullable
+ public FontConfig.FontFamily getFontFamily() {
+ return mFontFamily;
+ }
+
@Override
public int describeContents() {
- return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ return mFd != null ? mFd.describeContents() : 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
dest.writeParcelable(mFd, flags);
dest.writeBlob(mSignature);
+ dest.writeParcelable(mFontFamily, flags);
}
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 08b1e24..5f5697a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -236,7 +236,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext,
+ return new BiometricTestSession(mContext, sensorId,
mService.createTestSession(sensorId, mContext.getOpPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 2b68989..1c35608 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -25,6 +25,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Log;
/**
* Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -33,7 +34,10 @@
*/
@TestApi
public class BiometricTestSession implements AutoCloseable {
+ private static final String TAG = "BiometricTestSession";
+
private final Context mContext;
+ private final int mSensorId;
private final ITestSession mTestSession;
// Keep track of users that were tested, which need to be cleaned up when finishing.
@@ -42,8 +46,10 @@
/**
* @hide
*/
- public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+ public BiometricTestSession(@NonNull Context context, int sensorId,
+ @NonNull ITestSession testSession) {
mContext = context;
+ mSensorId = sensorId;
mTestSession = testSession;
mTestedUsers = new ArraySet<>();
setTestHalEnabled(true);
@@ -61,6 +67,7 @@
@RequiresPermission(TEST_BIOMETRIC)
private void setTestHalEnabled(boolean enabled) {
try {
+ Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
mTestSession.setTestHalEnabled(enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8fe7158..8451ded 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1233,6 +1233,8 @@
int sequenceId) {
synchronized (mInterfaceLock) {
if (mInternalRepeatingRequestEnabled) {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
resumeInternalRepeatingRequest(true);
}
}
@@ -1263,7 +1265,12 @@
mRequestUpdatedNeeded = false;
resumeInternalRepeatingRequest(false);
} else if (mInternalRepeatingRequestEnabled) {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
resumeInternalRepeatingRequest(true);
+ } else {
+ mRepeatingRequestImageReader.setOnImageAvailableListener(
+ new ImageLoopbackCallback(), mHandler);
}
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index f175e7b..2d4b2cc 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -42,6 +42,12 @@
*/
public static final int INVALID_DEVICE_STATE = -1;
+ /** The minimum allowed device state identifier. */
+ public static final int MINIMUM_DEVICE_STATE = 0;
+
+ /** The maximum allowed device state identifier. */
+ public static final int MAXIMUM_DEVICE_STATE = 255;
+
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index f3da6a9..886a8c1 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -833,67 +833,6 @@
}
/**
- * @hide
- */
- public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
- switch (acquireInfo) {
- case FACE_ACQUIRED_GOOD:
- return null;
- case FACE_ACQUIRED_INSUFFICIENT:
- return context.getString(R.string.face_acquired_insufficient);
- case FACE_ACQUIRED_TOO_BRIGHT:
- return context.getString(R.string.face_acquired_too_bright);
- case FACE_ACQUIRED_TOO_DARK:
- return context.getString(R.string.face_acquired_too_dark);
- case FACE_ACQUIRED_TOO_CLOSE:
- return context.getString(R.string.face_acquired_too_close);
- case FACE_ACQUIRED_TOO_FAR:
- return context.getString(R.string.face_acquired_too_far);
- case FACE_ACQUIRED_TOO_HIGH:
- return context.getString(R.string.face_acquired_too_high);
- case FACE_ACQUIRED_TOO_LOW:
- return context.getString(R.string.face_acquired_too_low);
- case FACE_ACQUIRED_TOO_RIGHT:
- return context.getString(R.string.face_acquired_too_right);
- case FACE_ACQUIRED_TOO_LEFT:
- return context.getString(R.string.face_acquired_too_left);
- case FACE_ACQUIRED_POOR_GAZE:
- return context.getString(R.string.face_acquired_poor_gaze);
- case FACE_ACQUIRED_NOT_DETECTED:
- return context.getString(R.string.face_acquired_not_detected);
- case FACE_ACQUIRED_TOO_MUCH_MOTION:
- return context.getString(R.string.face_acquired_too_much_motion);
- case FACE_ACQUIRED_RECALIBRATE:
- return context.getString(R.string.face_acquired_recalibrate);
- case FACE_ACQUIRED_TOO_DIFFERENT:
- return context.getString(R.string.face_acquired_too_different);
- case FACE_ACQUIRED_TOO_SIMILAR:
- return context.getString(R.string.face_acquired_too_similar);
- case FACE_ACQUIRED_PAN_TOO_EXTREME:
- return context.getString(R.string.face_acquired_pan_too_extreme);
- case FACE_ACQUIRED_TILT_TOO_EXTREME:
- return context.getString(R.string.face_acquired_tilt_too_extreme);
- case FACE_ACQUIRED_ROLL_TOO_EXTREME:
- return context.getString(R.string.face_acquired_roll_too_extreme);
- case FACE_ACQUIRED_FACE_OBSCURED:
- return context.getString(R.string.face_acquired_obscured);
- case FACE_ACQUIRED_START:
- return null;
- case FACE_ACQUIRED_SENSOR_DIRTY:
- return context.getString(R.string.face_acquired_sensor_dirty);
- case FACE_ACQUIRED_VENDOR: {
- String[] msgArray = context.getResources().getStringArray(
- R.array.face_acquired_vendor);
- if (vendorCode < msgArray.length) {
- return msgArray[vendorCode];
- }
- }
- }
- Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
- return null;
- }
-
- /**
* Used so BiometricPrompt can map the face ones onto existing public constants.
* @hide
*/
@@ -1387,7 +1326,7 @@
final int acquireInfo = frame.getData().getAcquiredInfo();
final int vendorCode = frame.getData().getVendorCode();
final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+ final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
// Ensure that only non-null help messages are sent.
@@ -1405,7 +1344,7 @@
final int acquireInfo = frame.getData().getAcquiredInfo();
final int vendorCode = frame.getData().getVendorCode();
final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAcquiredString(mContext, acquireInfo, vendorCode);
+ final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
}
}
@@ -1415,4 +1354,124 @@
? vendorCode + FACE_ACQUIRED_VENDOR_BASE
: acquireInfo;
}
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static String getAuthHelpMessage(Context context, int acquireInfo, int vendorCode) {
+ switch (acquireInfo) {
+ // No help message is needed for a good capture.
+ case FACE_ACQUIRED_GOOD:
+ case FACE_ACQUIRED_START:
+ return null;
+
+ // Consolidate positional feedback to reduce noise during authentication.
+ case FACE_ACQUIRED_NOT_DETECTED:
+ case FACE_ACQUIRED_TOO_CLOSE:
+ case FACE_ACQUIRED_TOO_FAR:
+ case FACE_ACQUIRED_TOO_HIGH:
+ case FACE_ACQUIRED_TOO_LOW:
+ case FACE_ACQUIRED_TOO_RIGHT:
+ case FACE_ACQUIRED_TOO_LEFT:
+ case FACE_ACQUIRED_POOR_GAZE:
+ case FACE_ACQUIRED_PAN_TOO_EXTREME:
+ case FACE_ACQUIRED_TILT_TOO_EXTREME:
+ case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_not_detected);
+
+ // Provide more detailed feedback for other soft errors.
+ case FACE_ACQUIRED_INSUFFICIENT:
+ return context.getString(R.string.face_acquired_insufficient);
+ case FACE_ACQUIRED_TOO_BRIGHT:
+ return context.getString(R.string.face_acquired_too_bright);
+ case FACE_ACQUIRED_TOO_DARK:
+ return context.getString(R.string.face_acquired_too_dark);
+ case FACE_ACQUIRED_TOO_MUCH_MOTION:
+ return context.getString(R.string.face_acquired_too_much_motion);
+ case FACE_ACQUIRED_RECALIBRATE:
+ return context.getString(R.string.face_acquired_recalibrate);
+ case FACE_ACQUIRED_TOO_DIFFERENT:
+ return context.getString(R.string.face_acquired_too_different);
+ case FACE_ACQUIRED_TOO_SIMILAR:
+ return context.getString(R.string.face_acquired_too_similar);
+ case FACE_ACQUIRED_FACE_OBSCURED:
+ return context.getString(R.string.face_acquired_obscured);
+ case FACE_ACQUIRED_SENSOR_DIRTY:
+ return context.getString(R.string.face_acquired_sensor_dirty);
+
+ // Find and return the appropriate vendor-specific message.
+ case FACE_ACQUIRED_VENDOR: {
+ String[] msgArray = context.getResources().getStringArray(
+ R.array.face_acquired_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+
+ Slog.w(TAG, "Unknown authentication acquired message: " + acquireInfo + ", " + vendorCode);
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static String getEnrollHelpMessage(Context context, int acquireInfo, int vendorCode) {
+ switch (acquireInfo) {
+ case FACE_ACQUIRED_GOOD:
+ case FACE_ACQUIRED_START:
+ return null;
+ case FACE_ACQUIRED_INSUFFICIENT:
+ return context.getString(R.string.face_acquired_insufficient);
+ case FACE_ACQUIRED_TOO_BRIGHT:
+ return context.getString(R.string.face_acquired_too_bright);
+ case FACE_ACQUIRED_TOO_DARK:
+ return context.getString(R.string.face_acquired_too_dark);
+ case FACE_ACQUIRED_TOO_CLOSE:
+ return context.getString(R.string.face_acquired_too_close);
+ case FACE_ACQUIRED_TOO_FAR:
+ return context.getString(R.string.face_acquired_too_far);
+ case FACE_ACQUIRED_TOO_HIGH:
+ return context.getString(R.string.face_acquired_too_high);
+ case FACE_ACQUIRED_TOO_LOW:
+ return context.getString(R.string.face_acquired_too_low);
+ case FACE_ACQUIRED_TOO_RIGHT:
+ return context.getString(R.string.face_acquired_too_right);
+ case FACE_ACQUIRED_TOO_LEFT:
+ return context.getString(R.string.face_acquired_too_left);
+ case FACE_ACQUIRED_POOR_GAZE:
+ return context.getString(R.string.face_acquired_poor_gaze);
+ case FACE_ACQUIRED_NOT_DETECTED:
+ return context.getString(R.string.face_acquired_not_detected);
+ case FACE_ACQUIRED_TOO_MUCH_MOTION:
+ return context.getString(R.string.face_acquired_too_much_motion);
+ case FACE_ACQUIRED_RECALIBRATE:
+ return context.getString(R.string.face_acquired_recalibrate);
+ case FACE_ACQUIRED_TOO_DIFFERENT:
+ return context.getString(R.string.face_acquired_too_different);
+ case FACE_ACQUIRED_TOO_SIMILAR:
+ return context.getString(R.string.face_acquired_too_similar);
+ case FACE_ACQUIRED_PAN_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_pan_too_extreme);
+ case FACE_ACQUIRED_TILT_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_tilt_too_extreme);
+ case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+ return context.getString(R.string.face_acquired_roll_too_extreme);
+ case FACE_ACQUIRED_FACE_OBSCURED:
+ return context.getString(R.string.face_acquired_obscured);
+ case FACE_ACQUIRED_SENSOR_DIRTY:
+ return context.getString(R.string.face_acquired_sensor_dirty);
+ case FACE_ACQUIRED_VENDOR: {
+ String[] msgArray = context.getResources().getStringArray(
+ R.array.face_acquired_vendor);
+ if (vendorCode < msgArray.length) {
+ return msgArray[vendorCode];
+ }
+ }
+ }
+ Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
+ return null;
+ }
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 188a2a4..a614ebf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -153,7 +153,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext,
+ return new BiometricTestSession(mContext, sensorId,
mService.createTestSession(sensorId, mContext.getOpPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index f4d8a65..a4817ae 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -74,6 +74,11 @@
}
@Override
+ public int getId() {
+ return mVibratorId;
+ }
+
+ @Override
public boolean hasVibrator() {
return true;
}
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index a381b02..d843407 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -16,9 +16,12 @@
package android.hardware.input;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.CombinedVibrationEffect;
import android.os.NullVibrator;
+import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.util.SparseArray;
@@ -86,6 +89,7 @@
}
}
+ @NonNull
@Override
public int[] getVibratorIds() {
synchronized (mVibrators) {
@@ -97,6 +101,7 @@
}
}
+ @NonNull
@Override
public Vibrator getVibrator(int vibratorId) {
synchronized (mVibrators) {
@@ -107,6 +112,7 @@
return NullVibrator.getInstance();
}
+ @NonNull
@Override
public Vibrator getDefaultVibrator() {
// Returns vibrator ID 0
@@ -119,7 +125,13 @@
}
@Override
- public void vibrate(CombinedVibrationEffect effect) {
+ public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes) {
mInputManager.vibrate(mDeviceId, effect, mToken);
}
+
+ @Override
+ public void cancel() {
+ mInputManager.cancelVibrate(mDeviceId, mToken);
+ }
}
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f2b39d..8f5c2a0 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 175220
-moltmann@google.com
badhri@google.com
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d6774d4..933256a 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -58,6 +58,9 @@
in LinkAddress localAddr,
in String callingPackage);
+ void setNetworkForTunnelInterface(
+ int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
void deleteTunnelInterface(int resourceId, in String callingPackage);
IpSecTransformResponse createTransform(
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 70bca30..98acd98 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -782,6 +782,42 @@
}
}
+ /**
+ * Update the underlying network for this IpSecTunnelInterface.
+ *
+ * <p>This new underlying network will be used for all transforms applied AFTER this call is
+ * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+ * this tunnel interface, traffic will still use the old SA, and be routed on the old
+ * underlying network.
+ *
+ * <p>To migrate IPsec tunnel mode traffic, a caller should:
+ *
+ * <ol>
+ * <li>Update the IpSecTunnelInterface’s underlying network.
+ * <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+ * IpSecTunnelInterface.
+ * </ol>
+ *
+ * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+ * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
+ * this method will throw an {@link IllegalArgumentException}.
+ */
+ // TODO: b/169171001 Update the documentation when transform migration is supported.
+ // The purpose of making updating network and applying transforms separate is to leave open
+ // the possibility to support lossless migration procedures. To do that, Android platform
+ // will need to support multiple inbound tunnel mode transforms, just like it can support
+ // multiple transport mode transforms.
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+ try {
+ mService.setNetworkForTunnelInterface(
+ mResourceId, underlyingNetwork, mOpPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
@NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
@NonNull Network underlyingNetwork)
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index e01e5ae..f7c1c4b 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -19,7 +19,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.system.ErrnoException;
-import android.system.Int32Ref;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructLinger;
@@ -65,14 +64,11 @@
public int available() throws IOException {
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
-
- Int32Ref avail = new Int32Ref(0);
try {
- Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
+ return Os.ioctlInt(myFd, OsConstants.FIONREAD);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
- return avail.value;
}
/** {@inheritDoc} */
@@ -134,7 +130,7 @@
public void write (byte[] b) throws IOException {
write(b, 0, b.length);
}
-
+
/** {@inheritDoc} */
@Override
public void write (byte[] b, int off, int len) throws IOException {
@@ -255,7 +251,7 @@
/** note timeout presently ignored */
protected void connect(LocalSocketAddress address, int timeout)
throws IOException
- {
+ {
if (fd == null) {
throw new IOException("socket not created");
}
@@ -339,7 +335,7 @@
* @throws IOException if socket has been closed or cannot be created.
*/
protected OutputStream getOutputStream() throws IOException
- {
+ {
if (fd == null) {
throw new IOException("socket not created");
}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index f2b466d..72a6e16 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -47,6 +47,7 @@
POWER_COMPONENT_SYSTEM_SERVICES,
POWER_COMPONENT_SENSORS,
POWER_COMPONENT_GNSS,
+ POWER_COMPONENT_SCREEN,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface PowerComponent {
@@ -63,8 +64,9 @@
public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
public static final int POWER_COMPONENT_SENSORS = 9;
public static final int POWER_COMPONENT_GNSS = 10;
+ public static final int POWER_COMPONENT_SCREEN = 13;
- public static final int POWER_COMPONENT_COUNT = 11;
+ public static final int POWER_COMPONENT_COUNT = 14;
public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -85,6 +87,7 @@
TIME_COMPONENT_MOBILE_RADIO,
TIME_COMPONENT_SENSORS,
TIME_COMPONENT_GNSS,
+ TIME_COMPONENT_SCREEN,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface TimeComponent {
@@ -101,8 +104,9 @@
public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
public static final int TIME_COMPONENT_SENSORS = 9;
public static final int TIME_COMPONENT_GNSS = 10;
+ public static final int TIME_COMPONENT_SCREEN = 13;
- public static final int TIME_COMPONENT_COUNT = 11;
+ public static final int TIME_COMPONENT_COUNT = 14;
public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc86a60..01a8901 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -994,6 +994,19 @@
*/
public abstract long getScreenOnEnergy();
+ /**
+ * Returns the energies used by this uid for each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+ * type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+ * supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
public static abstract class Sensor {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2511,6 +2524,19 @@
*/
public abstract long getScreenDozeEnergy();
+ /**
+ * Returns the energies used for each
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+ * type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+ * supported.
+ *
+ * {@hide}
+ */
+ public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
@@ -5268,6 +5294,15 @@
pw.print(" flash=");
printmAh(pw, bs.flashlightPowerMah);
}
+ if (bs.customMeasuredPowerMah != null) {
+ for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) {
+ final double customPowerMah = bs.customMeasuredPowerMah[idx];
+ if (customPowerMah != 0) {
+ pw.print(" custom[" + idx + "]=");
+ printmAh(pw, customPowerMah);
+ }
+ }
+ }
pw.print(" )");
}
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index cb4e9cb..c8e682c 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.util.SparseArray;
import com.android.internal.util.Preconditions;
@@ -86,7 +87,20 @@
return 0;
}
- /** @hide */
+ /**
+ * Gets the estimated duration of the combined vibration in milliseconds.
+ *
+ * <p>For synced combinations this means the maximum duration of any individual {@link
+ * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+ *
+ * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
+ * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
+ * Prebaked effects where the length is device and potentially run-time dependent), this returns
+ * -1.
+ *
+ * @hide
+ */
+ @TestApi
public abstract long getDuration();
/** @hide */
@@ -256,6 +270,7 @@
*
* @hide
*/
+ @TestApi
public static final class Mono extends CombinedVibrationEffect {
private final VibrationEffect mEffect;
@@ -267,6 +282,7 @@
mEffect = effect;
}
+ @NonNull
public VibrationEffect getEffect() {
return mEffect;
}
@@ -282,6 +298,7 @@
mEffect.validate();
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
return true;
@@ -307,7 +324,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_MONO);
mEffect.writeToParcel(out, flags);
}
@@ -335,6 +357,7 @@
*
* @hide
*/
+ @TestApi
public static final class Stereo extends CombinedVibrationEffect {
/** Mapping vibrator ids to effects. */
@@ -357,6 +380,7 @@
}
/** Effects to be performed in sync, where each key represents the vibrator id. */
+ @NonNull
public SparseArray<VibrationEffect> getEffects() {
return mEffects;
}
@@ -394,6 +418,7 @@
}
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
return mEffects.indexOfKey(vibratorId) >= 0;
@@ -418,7 +443,7 @@
@Override
public int hashCode() {
- return Objects.hash(mEffects);
+ return mEffects.contentHashCode();
}
@Override
@@ -427,7 +452,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_STEREO);
out.writeInt(mEffects.size());
for (int i = 0; i < mEffects.size(); i++) {
@@ -459,6 +489,7 @@
*
* @hide
*/
+ @TestApi
public static final class Sequential extends CombinedVibrationEffect {
private final List<CombinedVibrationEffect> mEffects;
private final List<Integer> mDelays;
@@ -480,11 +511,13 @@
}
/** Effects to be performed in sequence. */
+ @NonNull
public List<CombinedVibrationEffect> getEffects() {
return mEffects;
}
/** Delay to be applied before each effect in {@link #getEffects()}. */
+ @NonNull
public List<Integer> getDelays() {
return mDelays;
}
@@ -542,6 +575,7 @@
}
}
+ /** @hide */
@Override
public boolean hasVibrator(int vibratorId) {
final int effectCount = mEffects.size();
@@ -564,7 +598,7 @@
@Override
public int hashCode() {
- return Objects.hash(mEffects);
+ return Objects.hash(mEffects, mDelays);
}
@Override
@@ -573,7 +607,12 @@
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
out.writeInt(mEffects.size());
for (int i = 0; i < mEffects.size(); i++) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 6a76da2..eac03dc 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1902,7 +1902,8 @@
* Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long
* array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
* Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
- * another array to also retrieve the separate memtrack size.
+ * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the
+ * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other.
*
* @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
* @hide
@@ -2589,6 +2590,13 @@
public static native long getIonPoolsSizeKb();
/**
+ * Return GPU DMA buffer usage in kB or -1 on error.
+ *
+ * @hide
+ */
+ public static native long getGpuDmaBufUsageKb();
+
+ /**
* Return DMA-BUF memory mapped by processes in kB.
* Notes:
* * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 6295124..124c0b0 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1359,8 +1359,17 @@
}
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
- uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
+ final String opPackageName = context.getOpPackageName();
+
+ if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid,
+ opPackageName) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+
+ // Legacy external storage access is granted to instrumentations invoked with
+ // "--no-isolated-storage" flag.
+ return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid,
+ opPackageName) == AppOpsManager.MODE_ALLOWED;
}
private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
deleted file mode 100644
index 1cd48dc..0000000
--- a/core/java/android/os/IVibratorService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.os.VibrationEffect;
-import android.os.VibrationAttributes;
-import android.os.VibratorInfo;
-import android.os.IVibratorStateListener;
-
-/** {@hide} */
-interface IVibratorService
-{
- boolean hasVibrator();
- boolean isVibrating();
- VibratorInfo getVibratorInfo();
- boolean registerVibratorStateListener(in IVibratorStateListener listener);
- boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
- boolean hasAmplitudeControl();
- void vibrate(int uid, String opPkg, in VibrationEffect effect,
- in VibrationAttributes attributes, String reason, IBinder token);
- void cancelVibrate(IBinder token);
-}
-
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 2559a33..dac1ede 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,7 +4,6 @@
per-file IExternalVibrationController.aidl = michaelwr@google.com
per-file IExternalVibratorService.aidl = michaelwr@google.com
per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file IVibratorService.aidl = michaelwr@google.com
per-file NullVibrator.java = michaelwr@google.com
per-file SystemVibrator.java = michaelwr@google.com
per-file VibrationEffect.aidl = michaelwr@google.com
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 30afe38..b42a495 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,19 +17,18 @@
package android.os;
import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -41,142 +40,46 @@
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
- private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
- private static final int VIBRATOR_PRESENT_YES = 1;
- private static final int VIBRATOR_PRESENT_NO = 2;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
- private @interface VibratorPresent {}
-
- private final IVibratorService mService;
- private final IVibratorManagerService mManagerService;
- private final Object mLock = new Object();
- private final Binder mToken = new Binder();
+ private final VibratorManager mVibratorManager;
private final Context mContext;
- @GuardedBy("mLock")
- private VibratorInfo mVibratorInfo;
- @GuardedBy("mLock")
- @VibratorPresent
- private int mVibratorPresent;
- @GuardedBy("mDelegates")
- private final ArrayMap<OnVibratorStateChangedListener,
- OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
+ @GuardedBy("mBrokenListeners")
+ private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
- @UnsupportedAppUsage
- public SystemVibrator() {
- mContext = null;
- mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
- mManagerService = IVibratorManagerService.Stub.asInterface(
- ServiceManager.getService("vibrator_manager"));
- }
+ @GuardedBy("mRegisteredListeners")
+ private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+ mRegisteredListeners = new ArrayMap<>();
@UnsupportedAppUsage
public SystemVibrator(Context context) {
super(context);
mContext = context;
- mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
- mManagerService = IVibratorManagerService.Stub.asInterface(
- ServiceManager.getService("vibrator_manager"));
+ mVibratorManager = mContext.getSystemService(VibratorManager.class);
}
@Override
public boolean hasVibrator() {
- try {
- synchronized (mLock) {
- if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
- mVibratorPresent =
- mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
- }
- return mVibratorPresent == VIBRATOR_PRESENT_YES;
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to query vibrator presence", e);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
return false;
}
+ return mVibratorManager.getVibratorIds().length > 0;
}
- /**
- * Check whether the vibrator is vibrating.
- *
- * @return True if the hardware is vibrating, otherwise false.
- */
@Override
public boolean isVibrating() {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
return false;
}
- try {
- return mService.isVibrating();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ for (int vibratorId : mVibratorManager.getVibratorIds()) {
+ if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+ return true;
+ }
}
return false;
}
- private class OnVibratorStateChangedListenerDelegate extends
- IVibratorStateListener.Stub {
- private final Executor mExecutor;
- private final OnVibratorStateChangedListener mListener;
-
- OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
- @NonNull Executor executor) {
- mExecutor = executor;
- mListener = listener;
- }
-
- @Override
- public void onVibrating(boolean isVibrating) {
- mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
- }
- }
-
- /**
- * Adds a listener for vibrator state change. If the listener was previously added and not
- * removed, this call will be ignored.
- *
- * @param listener Listener to be added.
- * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
- */
- @Override
- public void addVibratorStateListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnVibratorStateChangedListener listener) {
- Objects.requireNonNull(listener);
- Objects.requireNonNull(executor);
- if (mService == null) {
- Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
- return;
- }
-
- synchronized (mDelegates) {
- // If listener is already registered, reject and return.
- if (mDelegates.containsKey(listener)) {
- Log.w(TAG, "Listener already registered.");
- return;
- }
- try {
- final OnVibratorStateChangedListenerDelegate delegate =
- new OnVibratorStateChangedListenerDelegate(listener, executor);
- if (!mService.registerVibratorStateListener(delegate)) {
- Log.w(TAG, "Failed to register vibrate state listener");
- return;
- }
- mDelegates.put(listener, delegate);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
- * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
- * If the listener was previously added and not removed, this call will be ignored.
- *
- * @param listener listener to be added
- */
@Override
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
@@ -187,88 +90,128 @@
addVibratorStateListener(mContext.getMainExecutor(), listener);
}
- /**
- * Removes the listener for vibrator state changes. If the listener was not previously
- * registered, this call will do nothing.
- *
- * @param listener Listener to be removed.
- */
@Override
- public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
- if (mService == null) {
- Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+ Objects.requireNonNull(executor);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
return;
}
- synchronized (mDelegates) {
- // Check if the listener is registered, otherwise will return.
- if (mDelegates.containsKey(listener)) {
- final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
- try {
- if (!mService.unregisterVibratorStateListener(delegate)) {
- Log.w(TAG, "Failed to unregister vibrate state listener");
- return;
- }
- mDelegates.remove(listener);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ AllVibratorsStateListener delegate = null;
+ try {
+ synchronized (mRegisteredListeners) {
+ // If listener is already registered, reject and return.
+ if (mRegisteredListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ delegate = new AllVibratorsStateListener(executor, listener);
+ delegate.register(mVibratorManager);
+ mRegisteredListeners.put(listener, delegate);
+ delegate = null;
+ }
+ } finally {
+ if (delegate != null && delegate.hasRegisteredListeners()) {
+ // The delegate listener was left in a partial state with listeners registered to
+ // some but not all vibrators. Keep track of this to try to unregister them later.
+ synchronized (mBrokenListeners) {
+ mBrokenListeners.add(delegate);
}
}
+ tryUnregisterBrokenListeners();
}
}
@Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+ return;
+ }
+ synchronized (mRegisteredListeners) {
+ if (mRegisteredListeners.containsKey(listener)) {
+ AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+ delegate.unregister(mVibratorManager);
+ mRegisteredListeners.remove(listener);
+ }
+ }
+ tryUnregisterBrokenListeners();
+ }
+
+ @Override
public boolean hasAmplitudeControl() {
- if (mService == null) {
- Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
return false;
}
- try {
- return mService.hasAmplitudeControl();
- } catch (RemoteException e) {
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ return false;
}
- return false;
+ for (int vibratorId : vibratorIds) {
+ if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
+ return false;
+ }
+ }
+ return true;
}
@Override
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
AudioAttributes attributes) {
- if (mManagerService == null) {
- Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
return false;
}
- try {
- VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
- CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
- return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set always-on effect.", e);
- }
- return false;
+ VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
}
@Override
public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
String reason, @NonNull VibrationAttributes attributes) {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager.");
return;
}
- try {
- mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
+ CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+ mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
}
@Override
public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
- VibratorInfo vibratorInfo = getVibratorInfo();
int[] supported = new int[effectIds.length];
- for (int i = 0; i < effectIds.length; i++) {
- supported[i] = vibratorInfo == null
- ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
- : vibratorInfo.isEffectSupported(effectIds[i]);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+ return supported;
+ }
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+ return supported;
+ }
+ int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
+ for (int i = 0; i < vibratorIds.length; i++) {
+ vibratorSupportMap[i] = mVibratorManager.getVibrator(
+ vibratorIds[i]).areEffectsSupported(effectIds);
+ }
+ Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
+ for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
+ for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+ int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
+ if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
+ supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+ break;
+ } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
+ supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+ }
+ }
}
return supported;
}
@@ -276,42 +219,169 @@
@Override
public boolean[] arePrimitivesSupported(
@NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
- VibratorInfo vibratorInfo = getVibratorInfo();
boolean[] supported = new boolean[primitiveIds.length];
- for (int i = 0; i < primitiveIds.length; i++) {
- supported[i] = vibratorInfo == null
- ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
+ Arrays.fill(supported, false);
+ return supported;
+ }
+ int[] vibratorIds = mVibratorManager.getVibratorIds();
+ if (vibratorIds.length == 0) {
+ Arrays.fill(supported, false);
+ return supported;
+ }
+ boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
+ for (int i = 0; i < vibratorIds.length; i++) {
+ vibratorSupportMap[i] = mVibratorManager.getVibrator(
+ vibratorIds[i]).arePrimitivesSupported(primitiveIds);
+ }
+ Arrays.fill(supported, true);
+ for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
+ for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+ if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
+ supported[primitiveIdx] = false;
+ break;
+ }
+ }
}
return supported;
}
@Override
public void cancel() {
- if (mService == null) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
return;
}
- try {
- mService.cancelVibrate(mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to cancel vibration.", e);
+ mVibratorManager.cancel();
+ }
+
+ /**
+ * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+ * that were left registered to vibrators after failures to register them to all vibrators.
+ *
+ * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+ * and also fails to unregister any previously registered single listeners to other vibrators.
+ *
+ * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+ * fail silently and attempt to unregister the same broken listener later.
+ */
+ private void tryUnregisterBrokenListeners() {
+ synchronized (mBrokenListeners) {
+ try {
+ for (int i = mBrokenListeners.size(); --i >= 0; ) {
+ mBrokenListeners.get(i).unregister(mVibratorManager);
+ mBrokenListeners.remove(i);
+ }
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Failed to unregister broken listener", e);
+ }
}
}
- @Nullable
- private VibratorInfo getVibratorInfo() {
- try {
+ /** Listener for a single vibrator state change. */
+ private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+ private final AllVibratorsStateListener mAllVibratorsListener;
+ private final int mVibratorIdx;
+
+ SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+ mAllVibratorsListener = listener;
+ mVibratorIdx = vibratorIdx;
+ }
+
+ @Override
+ public void onVibratorStateChanged(boolean isVibrating) {
+ mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+ }
+ }
+
+ /** Listener for all vibrators state change. */
+ private static class AllVibratorsStateListener {
+ private final Object mLock = new Object();
+ private final Executor mExecutor;
+ private final OnVibratorStateChangedListener mDelegate;
+
+ @GuardedBy("mLock")
+ private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+ new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private int mInitializedMask;
+ @GuardedBy("mLock")
+ private int mVibratingMask;
+
+ AllVibratorsStateListener(@NonNull Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ mExecutor = executor;
+ mDelegate = listener;
+ }
+
+ boolean hasRegisteredListeners() {
synchronized (mLock) {
- if (mVibratorInfo != null) {
- return mVibratorInfo;
- }
- if (mService == null) {
- return null;
- }
- return mVibratorInfo = mService.getVibratorInfo();
+ return mVibratorListeners.size() > 0;
}
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to query vibrator info");
- throw e.rethrowFromSystemServer();
+ }
+
+ void register(VibratorManager vibratorManager) {
+ int[] vibratorIds = vibratorManager.getVibratorIds();
+ synchronized (mLock) {
+ for (int i = 0; i < vibratorIds.length; i++) {
+ int vibratorId = vibratorIds[i];
+ SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+ try {
+ vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+ listener);
+ mVibratorListeners.put(vibratorId, listener);
+ } catch (RuntimeException e) {
+ try {
+ unregister(vibratorManager);
+ } catch (RuntimeException e1) {
+ Log.w(TAG,
+ "Failed to unregister listener while recovering from a failed "
+ + "register call", e1);
+ }
+ throw e;
+ }
+ }
+ }
+ }
+
+ void unregister(VibratorManager vibratorManager) {
+ synchronized (mLock) {
+ for (int i = mVibratorListeners.size(); --i >= 0; ) {
+ int vibratorId = mVibratorListeners.keyAt(i);
+ SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+ vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+ mVibratorListeners.removeAt(i);
+ }
+ }
+ }
+
+ void onVibrating(int vibratorIdx, boolean vibrating) {
+ mExecutor.execute(() -> {
+ boolean anyVibrating;
+ synchronized (mLock) {
+ int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+ int vibratorMask = 1 << vibratorIdx;
+ if ((mInitializedMask & vibratorMask) == 0) {
+ // First state report for this vibrator, set vibrating initial value.
+ mInitializedMask |= vibratorMask;
+ mVibratingMask |= vibrating ? vibratorMask : 0;
+ } else {
+ // Flip vibrating value, if changed.
+ boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
+ if (prevVibrating != vibrating) {
+ mVibratingMask ^= vibratorMask;
+ }
+ }
+ if (mInitializedMask != allInitializedMask) {
+ // Wait for all vibrators initial state to be reported before delegating.
+ return;
+ }
+ anyVibrating = mVibratingMask != 0;
+ }
+ mDelegate.onVibratorStateChanged(anyVibrating);
+ });
}
}
}
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..b528eb1
--- /dev/null
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -0,0 +1,360 @@
+/*
+ * 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.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+ private static final String TAG = "VibratorManager";
+
+ private final IVibratorManagerService mService;
+ private final Context mContext;
+ private final Binder mToken = new Binder();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private int[] mVibratorIds;
+ @GuardedBy("mLock")
+ private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+ OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public SystemVibratorManager(Context context) {
+ super(context);
+ mContext = context;
+ mService = IVibratorManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+ }
+
+ @NonNull
+ @Override
+ public int[] getVibratorIds() {
+ synchronized (mLock) {
+ if (mVibratorIds != null) {
+ return mVibratorIds;
+ }
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+ } else {
+ return mVibratorIds = mService.getVibratorIds();
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return new int[0];
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getVibrator(int vibratorId) {
+ synchronized (mLock) {
+ Vibrator vibrator = mVibrators.get(vibratorId);
+ if (vibrator != null) {
+ return vibrator;
+ }
+ VibratorInfo info = null;
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+ } else {
+ info = mService.getVibratorInfo(vibratorId);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ if (info != null) {
+ vibrator = new SingleVibrator(info);
+ mVibrators.put(vibratorId, vibrator);
+ } else {
+ vibrator = NullVibrator.getInstance();
+ }
+ return vibrator;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Vibrator getDefaultVibrator() {
+ return mContext.getSystemService(Vibrator.class);
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+ return false;
+ }
+ try {
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration.", e);
+ }
+ }
+
+ /** Listener for vibrations on a single vibrator. */
+ private static class OnVibratorStateChangedListenerDelegate extends
+ IVibratorStateListener.Stub {
+ private final Executor mExecutor;
+ private final Vibrator.OnVibratorStateChangedListener mListener;
+
+ OnVibratorStateChangedListenerDelegate(
+ @NonNull Vibrator.OnVibratorStateChangedListener listener,
+ @NonNull Executor executor) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onVibrating(boolean isVibrating) {
+ mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+ }
+ }
+
+ /** Controls vibrations on a single vibrator. */
+ private final class SingleVibrator extends Vibrator {
+ private final VibratorInfo mVibratorInfo;
+
+ SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+ mVibratorInfo = vibratorInfo;
+ }
+
+ @Override
+ public int getId() {
+ return mVibratorInfo.getId();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return mVibratorInfo.hasAmplitudeControl();
+ }
+
+ @NonNull
+ @Override
+ public int[] areEffectsSupported(@NonNull int... effectIds) {
+ int[] supported = new int[effectIds.length];
+ for (int i = 0; i < effectIds.length; i++) {
+ supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
+ }
+ return supported;
+ }
+
+ @Override
+ public boolean[] arePrimitivesSupported(
+ @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+ boolean[] supported = new boolean[primitiveIds.length];
+ for (int i = 0; i < primitiveIds.length; i++) {
+ supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+ }
+ return supported;
+ }
+
+ @Override
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return false;
+ }
+ try {
+ VibrationAttributes attr = new VibrationAttributes.Builder(
+ attributes, effect).build();
+ CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+ .addVibrator(mVibratorInfo.getId(), effect)
+ .combine();
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+ @NonNull VibrationAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return;
+ }
+ try {
+ CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+ .addVibrator(mVibratorInfo.getId(), vibe)
+ .combine();
+ mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
+ + "; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
+ }
+ }
+
+ @Override
+ public boolean isVibrating() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.isVibrating(mVibratorInfo.getId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ @Override
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mContext == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+ return;
+ }
+ addVibratorStateListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ Objects.requireNonNull(executor);
+ if (mService == null) {
+ Log.w(TAG,
+ "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+ + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // If listener is already registered, reject and return.
+ if (mListeners.containsKey(listener)) {
+ Log.w(TAG, "Listener already registered.");
+ return;
+ }
+ try {
+ OnVibratorStateChangedListenerDelegate delegate =
+ new OnVibratorStateChangedListenerDelegate(listener, executor);
+ if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+ Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.put(listener, delegate);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ @Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mService == null) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId() + "; no vibrator service.");
+ return;
+ }
+ synchronized (mLock) {
+ // Check if the listener is registered, otherwise will return.
+ if (mListeners.containsKey(listener)) {
+ OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+ try {
+ if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+ delegate)) {
+ Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+ + mVibratorInfo.getId());
+ return;
+ }
+ mListeners.remove(listener);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index d1ca6a5..a828077 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -99,12 +99,13 @@
private boolean mExcludeFromBatteryUsageStats;
public Builder(int customPowerComponentCount, int customTimeComponentCount,
- BatteryStats.Uid batteryStatsUid) {
+ @NonNull BatteryStats.Uid batteryStatsUid) {
super(customPowerComponentCount, customTimeComponentCount);
mBatteryStatsUid = batteryStatsUid;
mUid = batteryStatsUid.getUid();
}
+ @NonNull
public BatteryStats.Uid getBatteryStatsUid() {
return mBatteryStatsUid;
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7d85d13..d6fa733 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,15 @@
}
/**
+ * Return the ID of this vibrator.
+ *
+ * @return The id of the vibrator controlled by this service.
+ */
+ public int getId() {
+ return -1;
+ }
+
+ /**
* Check whether the hardware has a vibrator.
*
* @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 1d5a587..5dd38b6 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -17,45 +17,123 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
/**
- * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
- * a synchronized fashion.
+ * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
*/
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
public abstract class VibratorManager {
- /** @hide */
- protected static final String TAG = "VibratorManager";
+ private static final String TAG = "VibratorManager";
+
+ private final String mPackageName;
/**
- * {@hide}
+ * @hide to prevent subclassing from outside of the framework
*/
public VibratorManager() {
+ mPackageName = ActivityThread.currentPackageName();
}
/**
- * This method lists all available actuator ids, returning a possible empty list.
- * If the device has only a single actuator, this should return a single entry with a
- * default id.
+ * @hide to prevent subclassing from outside of the framework
+ */
+ protected VibratorManager(Context context) {
+ mPackageName = context.getOpPackageName();
+ }
+
+ /**
+ * List all available vibrator ids, returning a possible empty list.
+ *
+ * @return An array containing the ids of the vibrators available on the device.
*/
@NonNull
public abstract int[] getVibratorIds();
/**
- * Returns a Vibrator service for given id.
- * This allows users to perform a vibration effect on a single actuator.
- */
+ * Retrieve a single vibrator by id.
+ *
+ * @param vibratorId The id of the vibrator to be retrieved.
+ * @return The vibrator with given {@code vibratorId}, never null.
+ */
@NonNull
public abstract Vibrator getVibrator(int vibratorId);
/**
- * Returns the system default Vibrator service.
- */
+ * Returns the system default Vibrator service.
+ */
@NonNull
public abstract Vibrator getDefaultVibrator();
/**
- * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
- * to the respective actuator, in sync.
+ * Configure an always-on haptics effect.
+ *
+ * @hide
*/
- public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+ * VibrationEffect} to be played on one or more vibrators.
+ * </p>
+ *
+ * @param effect an array of longs of times for which to turn the vibrator on or off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibrationEffect effect) {
+ vibrate(effect, null);
+ }
+
+ /**
+ * Vibrate with a given combination of effects.
+ *
+ * <p>
+ * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+ * VibrationEffect} to be played on one or more vibrators.
+ * </p>
+ *
+ * @param effect an array of longs of times for which to turn the vibrator on or off.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public final void vibrate(@NonNull CombinedVibrationEffect effect,
+ @Nullable VibrationAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+ }
+
+ /**
+ * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
+ * caller to specify the vibration is owned by someone else and set reason for vibration.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+ String reason, @Nullable VibrationAttributes attributes);
+
+ /**
+ * Turn all the vibrators off.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void cancel();
}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 0ff68fc..0589994 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -241,6 +241,13 @@
}
/**
+ * Checks if device supports V2 calls (e.g. PerUid).
+ */
+ public static boolean isV2Available() {
+ return nativeIsV2Available();
+ }
+
+ /**
* Checks if Incremental installations are allowed.
* A developer can disable Incremental installations by setting the property.
*/
@@ -439,6 +446,7 @@
/* Native methods */
private static native boolean nativeIsEnabled();
+ private static native boolean nativeIsV2Available();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 137825
-moltmann@google.com
evanseverson@google.com
ntmyren@google.com
zhanghai@google.com
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 85e9fdb..0e35ef9 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -693,13 +693,14 @@
for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
OpUsage usage = rawUsages.get(usageNum);
+ // If this attribution is a proxy, remove it
+ if (toRemoveProxies.contains(usage.toPackageAttr())) {
+ continue;
+ }
+
// If this attribution has a special attribution, do not remove it
if (specialAttributions.contains(usage.toPackageAttr())) {
deDuped.add(usage);
- }
-
- // If this attribution is a proxy, remove it
- if (toRemoveProxies.contains(usage.toPackageAttr())) {
continue;
}
diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permissionpresenterservice/OWNERS
+++ b/core/java/android/permissionpresenterservice/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 137825
-moltmann@google.com
evanseverson@google.com
ntmyren@google.com
zhanghai@google.com
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/pdf/OWNERS
+++ b/core/java/android/print/pdf/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/recommendation/OWNERS
+++ b/core/java/android/printservice/recommendation/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java
index 6fdfaab..9de75ca 100644
--- a/core/java/android/service/notification/NotificationListenerFilter.java
+++ b/core/java/android/service/notification/NotificationListenerFilter.java
@@ -20,6 +20,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -31,7 +32,8 @@
*/
public class NotificationListenerFilter implements Parcelable {
private int mAllowedNotificationTypes;
- private ArraySet<String> mDisallowedPackages;
+ // VersionedPackage is holding the pkg name and pkg uid
+ private ArraySet<VersionedPackage> mDisallowedPackages;
public NotificationListenerFilter() {
mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS
@@ -41,7 +43,7 @@
mDisallowedPackages = new ArraySet<>();
}
- public NotificationListenerFilter(int types, ArraySet<String> pkgs) {
+ public NotificationListenerFilter(int types, ArraySet<VersionedPackage> pkgs) {
mAllowedNotificationTypes = types;
mDisallowedPackages = pkgs;
}
@@ -51,7 +53,8 @@
*/
protected NotificationListenerFilter(Parcel in) {
mAllowedNotificationTypes = in.readInt();
- mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader());
+ mDisallowedPackages = (ArraySet<VersionedPackage>) in.readArraySet(
+ VersionedPackage.class.getClassLoader());
}
@Override
@@ -77,7 +80,7 @@
return (mAllowedNotificationTypes & type) != 0;
}
- public boolean isPackageAllowed(String pkg) {
+ public boolean isPackageAllowed(VersionedPackage pkg) {
return !mDisallowedPackages.contains(pkg);
}
@@ -85,7 +88,7 @@
return mAllowedNotificationTypes;
}
- public ArraySet<String> getDisallowedPackages() {
+ public ArraySet<VersionedPackage> getDisallowedPackages() {
return mDisallowedPackages;
}
@@ -93,10 +96,18 @@
mAllowedNotificationTypes = types;
}
- public void setDisallowedPackages(ArraySet<String> pkgs) {
+ public void setDisallowedPackages(ArraySet<VersionedPackage> pkgs) {
mDisallowedPackages = pkgs;
}
+ public void removePackage(VersionedPackage pkg) {
+ mDisallowedPackages.remove(pkg);
+ }
+
+ public void addPackage(VersionedPackage pkg) {
+ mDisallowedPackages.add(pkg);
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 64cddc3..f66f85b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -80,6 +80,10 @@
* <intent-filter>
* <action android:name="android.service.notification.NotificationListenerService" />
* </intent-filter>
+ * <meta-data
+ * android:name="android.service.notification.default_filter_types"
+ * android:value="1,2">
+ * </meta-data>
* </service></pre>
*
* <p>The service should wait for the {@link #onListenerConnected()} event
@@ -103,6 +107,21 @@
private final String TAG = getClass().getSimpleName();
/**
+ * The name of the {@code meta-data} tag containing a comma separated list of default
+ * integer notification types that should be provided to this listener. See
+ * {@link #FLAG_FILTER_TYPE_ONGOING},
+ * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
+ * and {@link #FLAG_FILTER_TYPE_SILENT}.
+ * <p>This value will only be read if the app has not previously specified a default type list,
+ * and if the user has not overridden the allowed types.</p>
+ * <p>An absent value means 'allow all types'.
+ * A present but empty value means 'allow no types'.</p>
+ *
+ */
+ public static final String META_DATA_DEFAULT_FILTER_TYPES
+ = "android.service.notification.default_filter_types";
+
+ /**
* {@link #getCurrentInterruptionFilter() Interruption filter} constant -
* Normal interruption filter.
*/
@@ -254,23 +273,19 @@
/**
* A flag value indicating that this notification listener can see conversation type
* notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
/**
* A flag value indicating that this notification listener can see altering type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_ALERTING = 2;
/**
* A flag value indicating that this notification listener can see silent type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_SILENT = 4;
/**
* A flag value indicating that this notification listener can see important
* ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
- * @hide
*/
public static final int FLAG_FILTER_TYPE_ONGOING = 8;
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index cbc0479..20f0598 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -32,7 +32,6 @@
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
- private static native void nativeKill(long ptr);
/** Create a new connection with the surface flinger. */
@UnsupportedAppUsage
@@ -44,22 +43,22 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mNativeClient != 0) {
- nativeDestroy(mNativeClient);
- }
+ kill();
} finally {
super.finalize();
}
}
/**
- * Forcibly detach native resources associated with this object.
- * Unlike destroy(), after this call any surfaces that were created
- * from the session will no longer work.
+ * Remove the reference to the native Session object. The native object may still exist if
+ * there are other references to it, but it cannot be accessed from this Java object anymore.
*/
@UnsupportedAppUsage
public void kill() {
- nativeKill(mNativeClient);
+ if (mNativeClient != 0) {
+ nativeDestroy(mNativeClient);
+ mNativeClient = 0;
+ }
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70ec2d4..6eba83f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1083,7 +1083,12 @@
if (creating) {
updateOpaqueFlag();
- mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ if (mUseBlastAdapter) {
+ createBlastSurfaceControls(viewRoot, name);
+ } else {
+ mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
+ }
} else if (mSurfaceControl == null) {
return;
}
@@ -1220,53 +1225,77 @@
* out, the old surface can be persevered until the new one has drawn by keeping the reference
* of the old SurfaceControl alive.
*/
- private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-
- SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+ private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) {
+ final SurfaceControl previousSurfaceControl = mSurfaceControl;
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
.setLocalOwnerView(this)
.setParent(viewRoot.getBoundsLayer())
- .setCallsite("SurfaceView.updateSurface");
+ .setCallsite("SurfaceView.updateSurface")
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFlags(mSurfaceFlags)
+ .setFormat(mFormat)
+ .build();
+ mBackgroundControl = createBackgroundControl(name);
+ return previousSurfaceControl;
+ }
- final SurfaceControl previousSurfaceControl;
- if (mUseBlastAdapter) {
- mSurfaceControl = builder
+ private SurfaceControl createBackgroundControl(String name) {
+ return new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for " + name)
+ .setLocalOwnerView(this)
+ .setOpaque(true)
+ .setColorLayer()
+ .setParent(mSurfaceControl)
+ .setCallsite("SurfaceView.updateSurface")
+ .build();
+ }
+
+ // We don't recreate the surface controls but only recreate the adapter. Since the blast layer
+ // is still alive, the old buffers will continue to be presented until replaced by buffers from
+ // the new adapter. This means we do not need to track the old surface control and destroy it
+ // after the client has drawn to avoid any flickers.
+ private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+ if (mSurfaceControl == null) {
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setLocalOwnerView(this)
+ .setParent(viewRoot.getBoundsLayer())
+ .setCallsite("SurfaceView.updateSurface")
.setContainerLayer()
.build();
- previousSurfaceControl = mBlastSurfaceControl;
+ }
+
+ if (mBlastSurfaceControl == null) {
mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name + "(BLAST)")
.setLocalOwnerView(this)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
.setParent(mSurfaceControl)
.setFlags(mSurfaceFlags)
.setHidden(false)
.setBLASTLayer()
.setCallsite("SurfaceView.updateSurface")
.build();
- mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat, true /* TODO */);
} else {
- previousSurfaceControl = mSurfaceControl;
- mSurfaceControl = builder
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFlags(mSurfaceFlags)
- .setFormat(mFormat)
- .build();
- mBlastSurfaceControl = null;
- mBlastBufferQueue = null;
+ // update blast layer
+ mTmpTransaction
+ .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+ .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
+ .show(mBlastSurfaceControl)
+ .apply();
}
- mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
- .setName("Background for " + name)
- .setLocalOwnerView(this)
- .setOpaque(true)
- .setColorLayer()
- .setParent(mSurfaceControl)
- .setCallsite("SurfaceView.updateSurface")
- .build();
- return previousSurfaceControl;
+ if (mBackgroundControl == null) {
+ mBackgroundControl = createBackgroundControl(name);
+ }
+
+ // Always recreate the IGBP for compatibility. This can be optimized in the future but
+ // the behavior change will need to be gated by SDK version.
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ }
+ mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight, mFormat, true /* TODO */);
}
private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4716141..6b13a29 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1898,11 +1898,12 @@
}
private void setBoundsLayerCrop(Transaction t) {
- // mWinFrame is already adjusted for surface insets. So offset it and use it as
- // the cropping bounds.
- mTempBoundsRect.set(mWinFrame);
- mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
- mWindowAttributes.surfaceInsets.top);
+ // Adjust of insets and update the bounds layer so child surfaces do not draw into
+ // the surface inset region.
+ mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+ mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+ mWindowAttributes.surfaceInsets.top,
+ mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
}
@@ -1913,25 +1914,18 @@
private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
if (mBoundsLayer != null) {
setBoundsLayerCrop(t);
- t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(),
- mSurface.getNextFrameNumber());
return true;
}
return false;
}
- private void prepareSurfaces(boolean sizeChanged) {
+ private void prepareSurfaces() {
final SurfaceControl.Transaction t = mTransaction;
final SurfaceControl sc = getSurfaceControl();
if (!sc.isValid()) return;
- boolean applyTransaction = updateBoundsLayer(t);
- if (sizeChanged) {
- applyTransaction = true;
- t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
- }
- if (applyTransaction) {
- t.apply();
+ if (updateBoundsLayer(t)) {
+ mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
}
}
@@ -3036,7 +3030,7 @@
// stopping, but on the client side it doesn't get stopped since it's restarted quick
// enough. WMS doesn't want to keep around old children since they will leak when the
// client creates new children.
- prepareSurfaces(surfaceSizeChanged);
+ prepareSurfaces();
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3070,8 +3064,10 @@
// via the WM relayout code path. We probably eventually
// want to synchronize transparent region hint changes
// with draws.
- mTransaction.setTransparentRegionHint(getSurfaceControl(),
- mTransparentRegion).apply();
+ SurfaceControl sc = getSurfaceControl();
+ if (sc.isValid()) {
+ mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 90c8e17..7b2bb73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,7 +115,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
@@ -1083,19 +1082,6 @@
}
}
- private static class ImeThreadFactory implements ThreadFactory {
- private final String mThreadName;
-
- ImeThreadFactory(String name) {
- mThreadName = name;
- }
-
- @Override
- public Thread newThread(Runnable r) {
- return new Thread(r, mThreadName);
- }
- }
-
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index c10ffbe..681fd14 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -17,6 +17,8 @@
package android.widget;
import android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -26,10 +28,14 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
+import android.util.AttributeSet;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This class performs the graphical effect used at the edges of scrollable widgets
* when the user scrolls beyond the content bounds in 2D space.
@@ -55,6 +61,24 @@
*/
public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
+ /**
+ * Use a color edge glow for the edge effect. From XML, use
+ * <code>android:edgeEffectType="glow"</code>.
+ */
+ public static final int TYPE_GLOW = 0;
+
+ /**
+ * Use a stretch for the edge effect. From XML, use
+ * <code>android:edgeEffectType="stretch"</code>.
+ */
+ public static final int TYPE_STRETCH = 1;
+
+ /** @hide */
+ @IntDef({TYPE_GLOW, TYPE_STRETCH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EdgeEffectType {
+ }
+
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
@@ -89,11 +113,14 @@
private float mGlowAlpha;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private float mGlowScaleY;
+ private float mDistance;
private float mGlowAlphaStart;
private float mGlowAlphaFinish;
private float mGlowScaleYStart;
private float mGlowScaleYFinish;
+ private float mDistanceStart;
+ private float mDistanceFinish;
private long mStartTime;
private float mDuration;
@@ -121,17 +148,29 @@
private float mBaseGlowScale;
private float mDisplacement = 0.5f;
private float mTargetDisplacement = 0.5f;
+ private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
/**
* Construct a new EdgeEffect with a theme appropriate for the provided context.
* @param context Context used to provide theming and resource information for the EdgeEffect
*/
public EdgeEffect(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new EdgeEffect with a theme appropriate for the provided context.
+ * @param context Context used to provide theming and resource information for the EdgeEffect
+ * @param attrs The attributes of the XML tag that is inflating the view
+ */
+ public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
mPaint.setAntiAlias(true);
final TypedArray a = context.obtainStyledAttributes(
- com.android.internal.R.styleable.EdgeEffect);
+ attrs, com.android.internal.R.styleable.EdgeEffect);
final int themeColor = a.getColor(
com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
+ mEdgeEffectType = a.getInt(
+ com.android.internal.R.styleable.EdgeEffect_edgeEffectType, TYPE_GLOW);
a.recycle();
mPaint.setColor((themeColor & 0xffffff) | 0x33000000);
mPaint.setStyle(Paint.Style.FILL);
@@ -223,6 +262,7 @@
mDuration = PULL_TIME;
mPullDistance += deltaDistance;
+ mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
final float absdd = Math.abs(deltaDistance);
mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -242,6 +282,56 @@
}
/**
+ * A view should call this when content is pulled away from an edge by the user.
+ * This will update the state of the current visual effect and its associated animation.
+ * The host view should always {@link android.view.View#invalidate()} after this
+ * and draw the results accordingly. This works similarly to {@link #onPull(float, float)},
+ * but returns the amount of <code>deltaDistance</code> that has been consumed. If the
+ * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this
+ * function will return 0 and the drawn value will remain unchanged.
+ *
+ * This method can be used to reverse the effect from a pull or absorb and partially consume
+ * some of a motion:
+ *
+ * <pre class="prettyprint">
+ * if (deltaY < 0) {
+ * float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth());
+ * deltaY -= consumed * getHeight();
+ * if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease();
+ * }
+ * </pre>
+ *
+ * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+ * 1.f (full length of the view) or negative values to express change
+ * back toward the edge reached to initiate the effect.
+ * @param displacement The displacement from the starting side of the effect of the point
+ * initiating the pull. In the case of touch this is the finger position.
+ * Values may be from 0-1.
+ * @return The amount of <code>deltaDistance</code> that was consumed, a number between
+ * 0 and <code>deltaDistance</code>.
+ */
+ public float onPullDistance(float deltaDistance, float displacement) {
+ float finalDistance = Math.max(0f, deltaDistance + mDistance);
+ float delta = finalDistance - mDistance;
+ onPull(delta, displacement);
+ return delta;
+ }
+
+ /**
+ * Returns the pull distance needed to be released to remove the showing effect.
+ * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and
+ * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}.
+ *
+ * This can be used in conjunction with {@link #onPullDistance(float, float)} to
+ * release the currently showing effect.
+ *
+ * @return The pull distance that must be released to remove the showing effect.
+ */
+ public float getDistance() {
+ return mDistance;
+ }
+
+ /**
* Call when the object is released after being pulled.
* This will begin the "decay" phase of the effect. After calling this method
* the host view should {@link android.view.View#invalidate()} and thereby
@@ -257,9 +347,11 @@
mState = STATE_RECEDE;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = RECEDE_TIME;
@@ -286,7 +378,7 @@
// nearly invisible.
mGlowAlphaStart = GLOW_ALPHA_START;
mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
+ mDistanceStart = mDistance;
// Growth for the size of the glow should be quadratic to properly
// respond
@@ -297,6 +389,9 @@
mGlowAlphaFinish = Math.max(
mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
mTargetDisplacement = 0.5f;
+
+ // Use glow values to estimate the absorption for stretch distance.
+ mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
}
/**
@@ -309,6 +404,17 @@
}
/**
+ * Sets the edge effect type to use. The default without a theme attribute set is
+ * {@link EdgeEffect#TYPE_GLOW}.
+ *
+ * @param type The edge effect type to use.
+ * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+ */
+ public void setType(@EdgeEffectType int type) {
+ mEdgeEffectType = type;
+ }
+
+ /**
* Set or clear the blend mode. A blend mode defines how source pixels
* (generated by a drawing command) are composited with the destination pixels
* (content of the render target).
@@ -333,6 +439,15 @@
return mPaint.getColor();
}
+ /**
+ * Return the edge effect type to use.
+ *
+ * @return The edge effect type to use.
+ * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+ */
+ public @EdgeEffectType int getType() {
+ return mEdgeEffectType;
+ }
/**
* Returns the blend mode. A blend mode defines how source pixels
@@ -402,6 +517,7 @@
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+ mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
if (t >= 1.f - EPSILON) {
@@ -413,10 +529,12 @@
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
// After absorb, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
@@ -425,10 +543,12 @@
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
+ mDistanceStart = mDistance;
// After pull, the glow should fade to nothing.
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
+ mDistanceFinish = 0.f;
break;
case STATE_PULL_DECAY:
mState = STATE_RECEDE;
@@ -439,4 +559,20 @@
}
}
}
+
+ /**
+ * @return The estimated pull distance as calculated from mGlowScaleY.
+ */
+ private float calculateDistanceFromGlowValues(float scale, float alpha) {
+ if (scale >= 1f) {
+ // It should asymptotically approach 1, but not reach there.
+ // Here, we're just choosing a value that is large.
+ return 1f;
+ }
+ if (scale > 0f) {
+ float v = 1f / 0.7f / (mGlowScaleY - 1f);
+ return v * v / mBounds.height();
+ }
+ return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
+ }
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 794b642..d59a415 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -69,9 +69,7 @@
private final TextView mTextView;
SpellCheckerSession mSpellCheckerSession;
- // We assume that the sentence level spell check will always provide better results than words.
- // Although word SC has a sequential option.
- private boolean mIsSentenceSpellCheckSupported;
+
final int mCookie;
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -134,7 +132,6 @@
| SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
| SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
| SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS);
- mIsSentenceSpellCheckSupported = true;
}
// Restore SpellCheckSpans in pool
@@ -318,13 +315,11 @@
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
isEditing = false;
- } else if (mIsSentenceSpellCheckSupported) {
+ } else {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
isEditing = selectionEnd <= start || selectionStart > end;
- } else {
- isEditing = selectionEnd < start || selectionStart > end;
}
if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
@@ -346,13 +341,8 @@
textInfos = textInfosCopy;
}
- if (mIsSentenceSpellCheckSupported) {
- mSpellCheckerSession.getSentenceSuggestions(
- textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
- } else {
- mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
- false /* TODO Set sequentialWords to true for initial spell check */);
- }
+ mSpellCheckerSession.getSentenceSuggestions(
+ textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
}
}
@@ -381,32 +371,30 @@
editable, suggestionsInfo, spellCheckSpan, offset, length);
} else {
// Valid word -- isInDictionary || !looksLikeTypo
- if (mIsSentenceSpellCheckSupported) {
- // Allow the spell checker to remove existing misspelled span by
- // overwriting the span over the same place
- final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
- final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
- final int start;
- final int end;
- if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
- start = spellCheckSpanStart + offset;
- end = start + length;
- } else {
- start = spellCheckSpanStart;
- end = spellCheckSpanEnd;
- }
- if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
- && end > start) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Remove existing misspelled span. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
- mSuggestionSpanCache.remove(key);
+ // Allow the spell checker to remove existing misspelled span by
+ // overwriting the span over the same place
+ final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
+ final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
+ final int start;
+ final int end;
+ if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
+ start = spellCheckSpanStart + offset;
+ end = start + length;
+ } else {
+ start = spellCheckSpanStart;
+ end = spellCheckSpanEnd;
+ }
+ if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
+ && end > start) {
+ final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+ final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+ if (tempSuggestionSpan != null) {
+ if (DBG) {
+ Log.i(TAG, "Remove existing misspelled span. "
+ + editable.subSequence(start, end));
}
+ editable.removeSpan(tempSuggestionSpan);
+ mSuggestionSpanCache.remove(key);
}
}
}
@@ -531,20 +519,16 @@
}
SuggestionSpan suggestionSpan =
new SuggestionSpan(mTextView.getContext(), suggestions, flags);
- // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
- // to share the logic of word level spell checker and sentence level spell checker
- if (mIsSentenceSpellCheckSupported) {
- final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
- final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
- if (tempSuggestionSpan != null) {
- if (DBG) {
- Log.i(TAG, "Cached span on the same position is cleard. "
- + editable.subSequence(start, end));
- }
- editable.removeSpan(tempSuggestionSpan);
+ final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+ final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+ if (tempSuggestionSpan != null) {
+ if (DBG) {
+ Log.i(TAG, "Cached span on the same position is cleard. "
+ + editable.subSequence(start, end));
}
- mSuggestionSpanCache.put(key, suggestionSpan);
+ editable.removeSpan(tempSuggestionSpan);
}
+ mSuggestionSpanCache.put(key, suggestionSpan);
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
@@ -599,15 +583,8 @@
public void parse() {
Editable editable = (Editable) mTextView.getText();
// Iterate over the newly added text and schedule new SpellCheckSpans
- final int start;
- if (mIsSentenceSpellCheckSupported) {
- // TODO: Find the start position of the sentence.
- // Set span with the context
- start = Math.max(
- 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
- } else {
- start = editable.getSpanStart(mRange);
- }
+ final int start = Math.max(
+ 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
final int end = editable.getSpanEnd(mRange);
@@ -633,155 +610,80 @@
return;
}
- // We need to expand by one character because we want to include the spans that
- // end/start at position start/end respectively.
- SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1,
- SpellCheckSpan.class);
- SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
- SuggestionSpan.class);
-
- int wordCount = 0;
boolean scheduleOtherSpellCheck = false;
- if (mIsSentenceSpellCheckSupported) {
- if (wordIteratorWindowEnd < end) {
- if (DBG) {
- Log.i(TAG, "schedule other spell check.");
- }
- // Several batches needed on that region. Cut after last previous word
- scheduleOtherSpellCheck = true;
+ if (wordIteratorWindowEnd < end) {
+ if (DBG) {
+ Log.i(TAG, "schedule other spell check.");
}
- int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
- boolean correct = spellCheckEnd != BreakIterator.DONE;
- if (correct) {
- spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
- correct = spellCheckEnd != BreakIterator.DONE;
- }
- if (!correct) {
- if (DBG) {
- Log.i(TAG, "Incorrect range span.");
- }
- stop();
- return;
- }
- do {
- // TODO: Find the start position of the sentence.
- int spellCheckStart = wordStart;
- boolean createSpellCheckSpan = true;
- // Cancel or merge overlapped spell check spans
- for (int i = 0; i < mLength; ++i) {
- final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
- if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
- continue;
- }
- final int spanStart = editable.getSpanStart(spellCheckSpan);
- final int spanEnd = editable.getSpanEnd(spellCheckSpan);
- if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
- // No need to merge
- continue;
- }
- if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
- // There is a completely overlapped spell check span
- // skip this span
- createSpellCheckSpan = false;
- if (DBG) {
- Log.i(TAG, "The range is overrapped. Skip spell check.");
- }
- break;
- }
- // This spellCheckSpan is replaced by the one we are creating
- editable.removeSpan(spellCheckSpan);
- spellCheckStart = Math.min(spanStart, spellCheckStart);
- spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
- }
-
- if (DBG) {
- Log.d(TAG, "addSpellCheckSpan: "
- + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
- + ", next = " + scheduleOtherSpellCheck + "\n"
- + editable.subSequence(spellCheckStart, spellCheckEnd));
- }
-
- // Stop spell checking when there are no characters in the range.
- if (spellCheckEnd < start) {
- break;
- }
- if (spellCheckEnd <= spellCheckStart) {
- Log.w(TAG, "Trying to spellcheck invalid region, from "
- + start + " to " + end);
- break;
- }
- if (createSpellCheckSpan) {
- addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
- }
- } while (false);
- wordStart = spellCheckEnd;
- } else {
- while (wordStart <= end) {
- if (wordEnd >= start && wordEnd > wordStart) {
- if (wordCount >= MAX_NUMBER_OF_WORDS) {
- scheduleOtherSpellCheck = true;
- break;
- }
- // A new word has been created across the interval boundaries with this
- // edit. The previous spans (that ended on start / started on end) are
- // not valid anymore and must be removed.
- if (wordStart < start && wordEnd > start) {
- removeSpansAt(editable, start, spellCheckSpans);
- removeSpansAt(editable, start, suggestionSpans);
- }
-
- if (wordStart < end && wordEnd > end) {
- removeSpansAt(editable, end, spellCheckSpans);
- removeSpansAt(editable, end, suggestionSpans);
- }
-
- // Do not create new boundary spans if they already exist
- boolean createSpellCheckSpan = true;
- if (wordEnd == start) {
- for (int i = 0; i < spellCheckSpans.length; i++) {
- final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
- if (spanEnd == start) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (wordStart == end) {
- for (int i = 0; i < spellCheckSpans.length; i++) {
- final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
- if (spanStart == end) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (createSpellCheckSpan) {
- addSpellCheckSpan(editable, wordStart, wordEnd);
- }
- wordCount++;
- }
-
- // iterate word by word
- int originalWordEnd = wordEnd;
- wordEnd = mWordIterator.following(wordEnd);
- if ((wordIteratorWindowEnd < end) &&
- (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
- wordIteratorWindowEnd =
- Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
- mWordIterator.setCharSequence(
- editable, originalWordEnd, wordIteratorWindowEnd);
- wordEnd = mWordIterator.following(originalWordEnd);
- }
- if (wordEnd == BreakIterator.DONE) break;
- wordStart = mWordIterator.getBeginning(wordEnd);
- if (wordStart == BreakIterator.DONE) {
- break;
- }
- }
+ // Several batches needed on that region. Cut after last previous word
+ scheduleOtherSpellCheck = true;
}
+ int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
+ boolean correct = spellCheckEnd != BreakIterator.DONE;
+ if (correct) {
+ spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
+ correct = spellCheckEnd != BreakIterator.DONE;
+ }
+ if (!correct) {
+ if (DBG) {
+ Log.i(TAG, "Incorrect range span.");
+ }
+ stop();
+ return;
+ }
+ do {
+ // TODO: Find the start position of the sentence.
+ int spellCheckStart = wordStart;
+ boolean createSpellCheckSpan = true;
+ // Cancel or merge overlapped spell check spans
+ for (int i = 0; i < mLength; ++i) {
+ final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
+ if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
+ continue;
+ }
+ final int spanStart = editable.getSpanStart(spellCheckSpan);
+ final int spanEnd = editable.getSpanEnd(spellCheckSpan);
+ if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
+ // No need to merge
+ continue;
+ }
+ if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
+ // There is a completely overlapped spell check span
+ // skip this span
+ createSpellCheckSpan = false;
+ if (DBG) {
+ Log.i(TAG, "The range is overrapped. Skip spell check.");
+ }
+ break;
+ }
+ // This spellCheckSpan is replaced by the one we are creating
+ editable.removeSpan(spellCheckSpan);
+ spellCheckStart = Math.min(spanStart, spellCheckStart);
+ spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
+ }
+
+ if (DBG) {
+ Log.d(TAG, "addSpellCheckSpan: "
+ + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
+ + ", next = " + scheduleOtherSpellCheck + "\n"
+ + editable.subSequence(spellCheckStart, spellCheckEnd));
+ }
+
+ // Stop spell checking when there are no characters in the range.
+ if (spellCheckEnd < start) {
+ break;
+ }
+ if (spellCheckEnd <= spellCheckStart) {
+ Log.w(TAG, "Trying to spellcheck invalid region, from "
+ + start + " to " + end);
+ break;
+ }
+ if (createSpellCheckSpan) {
+ addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
+ }
+ } while (false);
+ wordStart = spellCheckEnd;
if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
// Update range span: start new spell check from last wordStart
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index b484dfa..2904a8c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -172,6 +172,22 @@
}
/**
+ * Update the LayoutParameters of the currently showing toast view. This is used for layout
+ * updates based on orientation changes.
+ */
+ public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin,
+ float verticalMargin, int gravity) {
+ checkState(mView != null, "Toast must be showing to update its layout parameters.");
+ Configuration config = mResources.getConfiguration();
+ mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
+ mParams.x = xOffset;
+ mParams.y = yOffset;
+ mParams.horizontalMargin = horizontalMargin;
+ mParams.verticalMargin = verticalMargin;
+ addToastView();
+ }
+
+ /**
* Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
* packageName} is a cross-user package.
*
@@ -221,18 +237,7 @@
adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
horizontalMargin, verticalMargin, removeWindowAnimations);
- if (mView.getParent() != null) {
- mWindowManager.removeView(mView);
- }
- try {
- mWindowManager.addView(mView, mParams);
- } catch (WindowManager.BadTokenException e) {
- // Since the notification manager service cancels the token right after it notifies us
- // to cancel the toast there is an inherent race and we may attempt to add a window
- // after the token has been invalidated. Let us hedge against that.
- Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
- return;
- }
+ addToastView();
trySendAccessibilityEvent(mView, mPackageName);
if (callback != null) {
try {
@@ -288,4 +293,19 @@
view.dispatchPopulateAccessibilityEvent(event);
mAccessibilityManager.sendAccessibilityEvent(event);
}
+
+ private void addToastView() {
+ if (mView.getParent() != null) {
+ mWindowManager.removeView(mView);
+ }
+ try {
+ mWindowManager.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ // Since the notification manager service cancels the token right after it notifies us
+ // to cancel the toast there is an inherent race and we may attempt to add a window
+ // after the token has been invalidated. Let us hedge against that.
+ Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+ return;
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index c2ee646..0152387 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -53,10 +53,8 @@
public static final String KEY_CUR_TASK = "cur_task";
/** Package of newly requested heavy-weight app. */
public static final String KEY_NEW_APP = "new_app";
- public static final String KEY_ACTIVITY_OPTIONS = "activity_options";
IntentSender mStartIntent;
- Bundle mActivityOptions;
boolean mHasResult;
String mCurApp;
int mCurTask;
@@ -67,9 +65,8 @@
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+
mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
- mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS);
mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
@@ -151,9 +148,9 @@
if (mHasResult) {
startIntentSenderForResult(mStartIntent, -1, null,
Intent.FLAG_ACTIVITY_FORWARD_RESULT,
- Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions);
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
} else {
- startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions);
+ startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
}
} catch (IntentSender.SendIntentException ex) {
Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 0ede1b8..e602cd2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -28,6 +28,7 @@
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.FileObserver;
@@ -504,7 +505,7 @@
final int pfdMode = ParcelFileDescriptor.parseMode(mode);
if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
- return ParcelFileDescriptor.open(file, pfdMode);
+ return openFileForRead(file);
} else {
try {
// When finished writing, kick off media scanner
@@ -519,6 +520,24 @@
}
}
+ private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException {
+ final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target);
+
+ // Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to
+ // transcode or not transcode can be made based upon the calling app's uid, and not based
+ // upon the Provider's uid.
+ final Bundle opts = new Bundle();
+ opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, Binder.getCallingUid());
+
+ final AssetFileDescriptor afd =
+ getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts);
+ if (afd == null) {
+ return null;
+ }
+
+ return afd.getParcelFileDescriptor();
+ }
+
/**
* Test if the file matches the query arguments.
*
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal;
+package com.android.internal.display;
import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e9bc84..cba6af9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -499,7 +499,7 @@
}
public String getPerfettoTrigger() {
- return String.format("interaction-jank-monitor-%d", mCujType);
+ return String.format("com.android.telemetry.interaction-jank-monitor-%d", mCujType);
}
public String getName() {
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 16b6f3a..1313090 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -83,7 +83,7 @@
private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
- return mAhToUJ(measuredEnergyUJ);
+ return uJtoMah(measuredEnergyUJ);
} else {
return mPowerEstimator.calculatePower(durationMs);
}
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index af61f91..9b88516 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -133,6 +133,7 @@
public double wakeLockPowerMah;
public double wifiPowerMah;
public double systemServiceCpuPowerMah;
+ public double[] customMeasuredPowerMah;
// Do not include this sipper in results because it is included
// in an aggregate sipper.
@@ -251,6 +252,17 @@
proportionalSmearMah += other.proportionalSmearMah;
totalSmearedPowerMah += other.totalSmearedPowerMah;
systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
+ if (other.customMeasuredPowerMah != null) {
+ if (customMeasuredPowerMah == null) {
+ customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length];
+ }
+ if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) {
+ // This should always be true.
+ for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) {
+ customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx];
+ }
+ }
+ }
}
/**
@@ -264,6 +276,11 @@
sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
+ systemServiceCpuPowerMah;
+ if (customMeasuredPowerMah != null) {
+ for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) {
+ totalPowerMah += customMeasuredPowerMah[idx];
+ }
+ }
totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index aa5015a..b20f50d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -347,6 +347,7 @@
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+ mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
mPowerCalculators.add(new UserPowerCalculator());
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6e41b3f..87820a8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1002,8 +1002,12 @@
int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
/**
- * Accumulated energy consumption, that is not attributed to individual uids, of various
- * consumers while on battery.
+ * Accumulated global (generally, device-wide total) energy consumption of various consumers
+ * while on battery.
+ * Its '<b>custom</b> energy buckets' correspond to the
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
* If energy consumer data is completely unavailable this will be null.
*/
@GuardedBy("this")
@@ -7157,20 +7161,34 @@
@Override
public long getScreenOnEnergy() {
- if (mGlobalMeasuredEnergyStats == null) {
- return ENERGY_DATA_UNAVAILABLE;
- }
- return mGlobalMeasuredEnergyStats
- .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+ return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
}
@Override
public long getScreenDozeEnergy() {
+ return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+ }
+
+ /**
+ * Returns the energy in microjoules that the given standard energy bucket consumed.
+ * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+ *
+ * @param bucket standard energy bucket of interest
+ * @return energy (in microjoules) used for this energy bucket
+ */
+ private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
if (mGlobalMeasuredEnergyStats == null) {
return ENERGY_DATA_UNAVAILABLE;
}
- return mGlobalMeasuredEnergyStats
- .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+ return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
+ }
+
+ @Override
+ public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() {
+ if (mGlobalMeasuredEnergyStats == null) {
+ return null;
+ }
+ return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
}
@Override public long getStartClockTime() {
@@ -7519,9 +7537,16 @@
*/
private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
- /** Measured energies attributed to this uid while on battery. */
- // We do not use a SparseArray<LongSamplingCounters> since it would cause lots of
- // unnecessary timebase references, and we're just going to use on-battery anyway...
+ /**
+ * Measured energies attributed to this uid while on battery.
+ * Its '<b>custom</b> energy buckets' correspond to the
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+ * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ *
+ * Will be null if energy consumer data is completely unavailable (in which case
+ * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid
+ * is 0 for every bucket.
+ */
private MeasuredEnergyStats mUidMeasuredEnergyStats;
/**
@@ -7941,6 +7966,13 @@
.updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
}
+ /** Adds the given energy to the given custom energy bucket for this uid. */
+ private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+ boolean accumulate) {
+ getOrCreateMeasuredEnergyStatsLocked()
+ .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+ }
+
/**
* Returns the energy used by this uid for a standard energy bucket of interest.
* @param bucket standard energy bucket of interest
@@ -7957,6 +7989,18 @@
return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
}
+ @Override
+ public long[] getCustomMeasuredEnergiesMicroJoules() {
+ if (mBsi.mGlobalMeasuredEnergyStats == null) {
+ return null;
+ }
+ if (mUidMeasuredEnergyStats == null) {
+ // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
+ return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()];
+ }
+ return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
+ }
+
/**
* Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
* since last marked. Also sets the mark time for both these timers.
@@ -12465,6 +12509,39 @@
}
/**
+ * Accumulate Custom energy bucket energy, globally and for each app.
+ *
+ * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+ * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+ * Data inside uidEnergies will not be modified (treated immutable).
+ */
+ public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+ long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+ if (DEBUG_ENERGY) {
+ Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+ + customEnergyBucket
+ + " with total energy " + totalEnergyUJ
+ + " and uid energies " + String.valueOf(uidEnergies));
+ }
+ if (mGlobalMeasuredEnergyStats == null) return;
+ if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+ mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+ if (uidEnergies == null) return;
+ final int numUids = uidEnergies.size();
+ for (int i = 0; i < numUids; i++) {
+ final int uidInt = mapUid(uidEnergies.keyAt(i));
+ final long uidEnergyUJ = uidEnergies.valueAt(i);
+ if (uidEnergyUJ == 0) continue;
+ // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this,
+ // or converse problem of not creating a new Uid if its first blame is recorded here.
+ final Uid uidObj = getUidStatsLocked(uidInt);
+ uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+ }
+ }
+
+ /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
@@ -12933,16 +13010,17 @@
mWakeLockAllocationsUs = null;
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidFreqTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.d(TAG, "Got freq readings for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
- mCpuUidFreqTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
@@ -13001,6 +13079,9 @@
}
}
});
+ for (int uid : uidsToRemove) {
+ mCpuUidFreqTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13047,21 +13128,25 @@
public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidActiveTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.w(TAG, "Got active times for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
- mCpuUidActiveTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
});
+ for (int uid : uidsToRemove) {
+ mCpuUidActiveTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13077,21 +13162,25 @@
public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
final long startTimeMs = mClocks.uptimeMillis();
final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+ final List<Integer> uidsToRemove = new ArrayList<>();
mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
- mCpuUidClusterTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
if (DEBUG) Slog.w(TAG, "Got cluster times for an isolated uid: " + uid);
return;
}
if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
if (DEBUG) Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
- mCpuUidClusterTimeReader.removeUid(uid);
+ uidsToRemove.add(uid);
return;
}
final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
});
+ for (int uid : uidsToRemove) {
+ mCpuUidClusterTimeReader.removeUid(uid);
+ }
final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -14454,8 +14543,13 @@
*/
@GuardedBy("this")
public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
- if (mGlobalMeasuredEnergyStats == null) return;
- dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
+ pw.printf("On battery measured energy stats (microjoules) \n");
+ if (mGlobalMeasuredEnergyStats == null) {
+ pw.printf(" Not supported on this device.\n");
+ return;
+ }
+
+ dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats);
int size = mUidStats.size();
for (int i = 0; i < size; i++) {
@@ -14472,7 +14566,8 @@
MeasuredEnergyStats stats) {
if (stats == null) return;
final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ");
- iPw.printf("On battery measured energy stats for %s:\n", name);
+ iPw.increaseIndent();
+ iPw.printf("%s:\n", name);
iPw.increaseIndent();
stats.dump(iPw);
iPw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 094724c..2798b72 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -72,6 +72,7 @@
mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+ mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
mPowerCalculators.add(new UserPowerCalculator());
}
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
new file mode 100644
index 0000000..4babe8d
--- /dev/null
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
+ * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ */
+public class CustomMeasuredPowerCalculator extends PowerCalculator {
+ public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
+ }
+
+ @Override
+ protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+ long rawUptimeUs, int statsType) {
+ updateCustomMeasuredPowerMah(app, u.getCustomMeasuredEnergiesMicroJoules());
+ }
+
+ private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredEnergiesUJ) {
+ sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredEnergiesUJ);
+ }
+
+ private double[] calculateMeasuredEnergiesMah(long[] measuredEnergiesUJ) {
+ if (measuredEnergiesUJ == null) {
+ return null;
+ }
+ final double[] measuredEnergiesMah = new double[measuredEnergiesUJ.length];
+ for (int i = 0; i < measuredEnergiesUJ.length; i++) {
+ measuredEnergiesMah[i] = uJtoMah(measuredEnergiesUJ[i]);
+ }
+ return measuredEnergiesMah;
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 141363f..7c45cc0 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -143,7 +143,11 @@
return String.format(Locale.ENGLISH, format, power);
}
- static double mAhToUJ(long energyUJ) {
+ static double uJtoMah(long energyUJ) {
+ if (energyUJ == 0) {
+ return 0;
+ }
+
// TODO(b/173765509): Convert properly. This is mJ / V * (h/3600s) = mAh with V = 3.7 fixed.
// Leaving for later since desired units of energy have yet to be decided
return energyUJ / 1000.0 / 3.7 / 3600;
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index bb1222e..c1dd7ce 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -21,7 +21,7 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.SystemBatteryConsumer;
-import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.Slog;
@@ -39,9 +39,17 @@
private static final String TAG = "ScreenPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ // Minimum amount of time the screen should be on to start smearing drain to apps
+ public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
+
private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
+ private static class PowerAndDuration {
+ public long durationMs;
+ public double powerMah;
+ }
+
public ScreenPowerCalculator(PowerProfile powerProfile) {
mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
@@ -52,17 +60,40 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- final long durationMs = computeDuration(batteryStats, rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED);
- final double powerMah = computePower(batteryStats, rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED, durationMs);
- if (powerMah != 0) {
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+ final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+ final boolean forceUsePowerProfileModel = (query.getFlags()
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
+
+ final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+ batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
+ forceUsePowerProfileModel);
+
+ builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
+ totalPowerAndDuration.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
+ totalPowerAndDuration.powerMah);
+
+ // Now deal with each app's UidBatteryConsumer. The results are stored in the
+ // BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
+ // but the method depends on the data source.
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ if (useEnergyData) {
+ final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
+ rawRealtimeUs);
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN,
+ appPowerAndDuration.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
+ appPowerAndDuration.powerMah);
+ }
+ } else {
+ smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
+ rawRealtimeUs);
}
- // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
- // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
}
/**
@@ -71,51 +102,79 @@
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-
- final long energyUJ = batteryStats.getScreenOnEnergy();
- final boolean isMeasuredDataAvailable = energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE;
-
- final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
- final double powerMah = getMeasuredOrComputedPower(
- energyUJ, batteryStats, rawRealtimeUs, statsType, durationMs);
- if (powerMah == 0) {
+ final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+ final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+ batteryStats, rawRealtimeUs, statsType, false);
+ if (totalPowerAndDuration.powerMah == 0) {
return;
}
// First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
- bs.usagePowerMah = powerMah;
- bs.usageTimeMs = durationMs;
+ bs.usagePowerMah = totalPowerAndDuration.powerMah;
+ bs.usageTimeMs = totalPowerAndDuration.durationMs;
bs.sumPower();
sippers.add(bs);
// Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
// field, which is considered smeared, but the method depends on the data source.
- if (isMeasuredDataAvailable) {
- super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+ if (useEnergyData) {
+ final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+ for (int i = sippers.size() - 1; i >= 0; i--) {
+ final BatterySipper app = sippers.get(i);
+ if (app.drainType == BatterySipper.DrainType.APP) {
+ calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj, rawRealtimeUs);
+ app.screenPowerMah = appPowerAndDuration.powerMah;
+ }
+ }
} else {
- smearScreenBatterySipper(sippers, bs);
+ smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
}
}
- @Override
- protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
- long rawUptimeUs, int statsType) {
+ /**
+ * Stores duration and power information in totalPowerAndDuration and returns true if measured
+ * energy data is available and should be used by the model.
+ */
+ private boolean calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
+ BatteryStats batteryStats, long rawRealtimeUs, int statsType,
+ boolean forceUsePowerProfileModel) {
+ totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
+ statsType);
+
+ if (!forceUsePowerProfileModel) {
+ final long energyUJ = batteryStats.getScreenOnEnergy();
+ if (energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+ totalPowerAndDuration.powerMah = uJtoMah(energyUJ);
+ return true;
+ }
+ }
+
+ totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
+ rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
+ return false;
+ }
+
+ private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
+ BatteryStats.Uid u, long rawRealtimeUs) {
+ appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
+
final long energyUJ = u.getScreenOnEnergy();
if (energyUJ < 0) {
Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
+ appPowerAndDuration.powerMah = 0;
return;
}
- if (energyUJ == 0) return;
- app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());
+
+ appPowerAndDuration.powerMah = uJtoMah(energyUJ);
}
- private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+ private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
}
- private double computePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType,
- long durationMs) {
+ private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
+ int statsType, long durationMs) {
double power = mScreenOnPowerEstimator.calculatePower(durationMs);
for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
final long brightnessTime =
@@ -131,35 +190,25 @@
return power;
}
- private double getMeasuredOrComputedPower(long measuredEnergyUJ,
- BatteryStats batteryStats, long rawRealtimeUs, int statsType, long durationMs) {
-
- if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
- return mAhToUJ(measuredEnergyUJ);
- } else {
- return computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
- }
- }
-
/**
* Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
* time, and store this in the {@link BatterySipper#screenPowerMah} field.
*/
@VisibleForTesting
- public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
-
+ public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
+ long rawRealtimeUs) {
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatteryStats.Uid uid = sippers.get(i).uidObj;
if (uid != null) {
- final long timeMs = getProcessForegroundTimeMs(uid);
+ final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
activityTimeArray.put(uid.getUid(), timeMs);
totalActivityTimeMs += timeMs;
}
}
- if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+ if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
final double totalScreenPowerMah = screenSipper.totalPowerMah;
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper sipper = sippers.get(i);
@@ -170,10 +219,37 @@
}
}
+ /**
+ * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
+ * time, and store this in the {@link BatterySipper#screenPowerMah} field.
+ */
+ private void smearScreenBatteryDrain(
+ SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
+ PowerAndDuration totalPowerAndDuration, long rawRealtimeUs) {
+ long totalActivityTimeMs = 0;
+ final SparseLongArray activityTimeArray = new SparseLongArray();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
+ final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
+ activityTimeArray.put(uid.getUid(), timeMs);
+ totalActivityTimeMs += timeMs;
+ }
+
+ if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
+ final double totalScreenPowerMah = totalPowerAndDuration.powerMah;
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ final long durationMs = activityTimeArray.get(app.getUid(), 0);
+ final double powerMah = totalScreenPowerMah * durationMs / totalActivityTimeMs;
+ app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah);
+ }
+ }
+ }
+
/** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
@VisibleForTesting
- public long getProcessForegroundTimeMs(BatteryStats.Uid uid) {
- final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
+ public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
long timeUs = 0;
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +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.internal.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
- int SUBSYSTEM_UNKNOWN = -1;
- int SUBSYSTEM_DISPLAY = 0;
- int NUMBER_SUBSYSTEMS = 1;
- String[] SUBSYSTEM_NAMES = {"display"};
-
-
- @IntDef(prefix = { "SUBSYSTEM_" }, value = {
- SUBSYSTEM_UNKNOWN,
- SUBSYSTEM_DISPLAY,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface MeasuredEnergySubsystem {}
-
- /**
- * Get the subsystem at an index in array.
- *
- * @param index into the array.
- * @return subsystem.
- */
- @MeasuredEnergySubsystem
- int getSubsystem(int index);
-
- /**
- * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
- *
- * @param index into the array.
- * @return energy (in microjoules) consumed since boot.
- */
- long getEnergy(int index);
-
- /**
- * Return number of subsystems in the array.
- */
- int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..d7b4d78 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -239,6 +238,7 @@
* Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
* Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
*/
+ @VisibleForTesting
public long getAccumulatedCustomBucketEnergy(int customBucket) {
if (!isValidCustomBucket(customBucket)) {
return ENERGY_DATA_UNAVAILABLE;
@@ -247,7 +247,18 @@
}
/**
- * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+ * Return accumulated energies (in microjoules) for all custom energy buckets since last reset.
+ */
+ public @NonNull long[] getAccumulatedCustomBucketEnergies() {
+ final long[] energies = new long[getNumberCustomEnergyBuckets()];
+ for (int bucket = 0; bucket < energies.length; bucket++) {
+ energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)];
+ }
+ return energies;
+ }
+
+ /**
+ * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
*/
public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
if (Display.isOnState(screenState)) {
@@ -405,7 +416,6 @@
/** Dump debug data. */
public void dump(PrintWriter pw) {
- pw.println("Accumulated energy since last reset (microjoules):");
pw.print(" ");
for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
pw.print(getBucketName(index));
@@ -432,6 +442,11 @@
return "CUSTOM_" + indexToCustomBucket(index);
}
+ /** Get the number of custom energy buckets on this device. */
+ public int getNumberCustomEnergyBuckets() {
+ return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS;
+ }
+
private static int customBucketToIndex(int customBucket) {
return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
}
@@ -450,7 +465,9 @@
return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
}
- private boolean isValidCustomBucket(int customBucket) {
+ /** Returns whether the given custom bucket is valid (exists) on this device. */
+ @VisibleForTesting
+ public boolean isValidCustomBucket(int customBucket) {
return customBucket >= 0
&& customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 488b18d..c6a040c 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -47,6 +47,13 @@
private CollectionUtils() { /* cannot be instantiated */ }
/**
+ * @see Collection#contains(Object)
+ */
+ public static <T> boolean contains(@Nullable Collection<T> collection, T element) {
+ return collection != null && collection.contains(element);
+ }
+
+ /**
* Returns a list of items from the provided list that match the given condition.
*
* This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f42f468..dc6880e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -219,7 +219,7 @@
}
private static String getTraceTriggerNameForAction(@Action int action) {
- return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
+ return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
}
public static boolean isEnabled(Context ctx) {
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
index 9c87c69..c758504 100644
--- a/core/java/com/android/internal/util/PerfettoTrigger.java
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
@@ -27,16 +28,28 @@
public class PerfettoTrigger {
private static final String TAG = "PerfettoTrigger";
private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+ private static final long THROTTLE_MILLIS = 60000;
+ private static volatile long sLastTriggerTime = -THROTTLE_MILLIS;
/**
* @param triggerName The name of the trigger. Must match the value defined in the AOT
* Perfetto config.
*/
public static void trigger(String triggerName) {
+ // Trace triggering has a non-negligible cost (fork+exec).
+ // To mitigate potential excessive triggering by the API client we ignore calls that happen
+ // too quickl after the most recent trigger.
+ long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime;
+ if (sinceLastTrigger < THROTTLE_MILLIS) {
+ Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger");
+ return;
+ }
+
try {
ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
- Process process = pb.start();
+ pb.start();
+ sLastTriggerTime = SystemClock.elapsedRealtime();
} catch (IOException e) {
Log.w(TAG, "Failed to trigger " + triggerName, e);
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index cb586d6..8982519 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -1236,6 +1236,8 @@
if (IncrementalManager.isFeatureEnabled()) {
addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
+ addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION,
+ IncrementalManager.isV2Available() ? 2 : 1);
}
if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8edc8a1..9510f27 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,6 +207,7 @@
],
shared_libs: [
+ "android.hardware.memtrack-unstable-ndk_platform",
"audioclient-types-aidl-cpp",
"audioflinger-aidl-cpp",
"av-types-aidl-cpp",
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index f1998a5..c7439f1 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -188,7 +188,7 @@
ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
- std::unique_ptr<const ApkAssets> apk_assets;
+ std::unique_ptr<ApkAssets> apk_assets;
switch (format) {
case FORMAT_APK: {
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
@@ -354,7 +354,7 @@
}
static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) {
- return reinterpret_cast<jlong>(&NativeDestroy);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeDestroy));
}
static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index c64174b..a7950f2 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,6 +33,7 @@
#include <string>
#include <vector>
+#include <aidl/android/hardware/memtrack/DeviceInfo.h>
#include <android-base/logging.h>
#include <bionic/malloc.h>
#include <debuggerd/client.h>
@@ -45,6 +46,7 @@
#include "jni.h"
#include <dmabufinfo/dmabuf_sysfs_stats.h>
#include <dmabufinfo/dmabufinfo.h>
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
@@ -520,14 +522,15 @@
}
if (outUssSwapPssRss != NULL) {
- if (env->GetArrayLength(outUssSwapPssRss) >= 1) {
+ int outLen = env->GetArrayLength(outUssSwapPssRss);
+ if (outLen >= 1) {
jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0);
if (outUssSwapPssRssArray != NULL) {
outUssSwapPssRssArray[0] = uss;
- if (env->GetArrayLength(outUssSwapPssRss) >= 2) {
+ if (outLen >= 2) {
outUssSwapPssRssArray[1] = swapPss;
}
- if (env->GetArrayLength(outUssSwapPssRss) >= 3) {
+ if (outLen >= 3) {
outUssSwapPssRssArray[2] = rss;
}
}
@@ -536,10 +539,20 @@
}
if (outMemtrack != NULL) {
- if (env->GetArrayLength(outMemtrack) >= 1) {
+ int outLen = env->GetArrayLength(outMemtrack);
+ if (outLen >= 1) {
jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
if (outMemtrackArray != NULL) {
outMemtrackArray[0] = memtrack;
+ if (outLen >= 2) {
+ outMemtrackArray[1] = graphics_mem.graphics;
+ }
+ if (outLen >= 3) {
+ outMemtrackArray[2] = graphics_mem.gl;
+ }
+ if (outLen >= 4) {
+ outMemtrackArray[3] = graphics_mem.other;
+ }
}
env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
}
@@ -838,6 +851,31 @@
return poolsSizeKb;
}
+static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) {
+ std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info;
+ if (!memtrack_gpu_device_info(&gpu_device_info)) {
+ return -1;
+ }
+
+ dmabufinfo::DmabufSysfsStats stats;
+ if (!GetDmabufSysfsStats(&stats)) {
+ return -1;
+ }
+
+ jlong sizeKb = 0;
+ const auto& importer_stats = stats.importer_info();
+ for (const auto& dev_info : gpu_device_info) {
+ const auto& importer_info = importer_stats.find(dev_info.name);
+ if (importer_info == importer_stats.end()) {
+ continue;
+ }
+
+ sizeKb += importer_info->second.size;
+ }
+
+ return sizeKb;
+}
+
static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
jlong dmabufPss = 0;
std::vector<dmabufinfo::DmaBuffer> dmabufs;
@@ -946,6 +984,8 @@
(void*)android_os_Debug_getIonHeapsSizeKb },
{ "getDmabufTotalExportedKb", "()J",
(void*)android_os_Debug_getDmabufTotalExportedKb },
+ { "getGpuDmaBufUsageKb", "()J",
+ (void*)android_os_Debug_getGpuDmaBufUsageKb },
{ "getIonPoolsSizeKb", "()J",
(void*)android_os_Debug_getIonPoolsSizeKb },
{ "getDmabufMappedSizeKb", "()J",
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index 44bff01..2384efa 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -30,6 +30,10 @@
return IncFs_IsEnabled();
}
+static jboolean nativeIsV2Available(JNIEnv* env, jobject clazz) {
+ return !!(IncFs_Features() & INCFS_FEATURE_V2);
+}
+
static jboolean nativeIsIncrementalPath(JNIEnv* env,
jobject clazz,
jstring javaPath) {
@@ -53,12 +57,12 @@
return result;
}
-static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
- {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
- (void*)nativeIsIncrementalPath},
- {"nativeUnsafeGetFileSignature",
- "(Ljava/lang/String;)[B",
- (void*)nativeUnsafeGetFileSignature}};
+static const JNINativeMethod method_table[] =
+ {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+ {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available},
+ {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
+ {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B",
+ (void*)nativeUnsafeGetFileSignature}};
int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index ac680f6..9746a07 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -199,7 +199,13 @@
for (;;) {
uint32_t publishedSeq;
bool handled;
- status_t status = mInputPublisher.receiveFinishedSignal(&publishedSeq, &handled);
+ std::function<void(uint32_t seq, bool handled, nsecs_t consumeTime)> callback =
+ [&publishedSeq, &handled](uint32_t inSeq, bool inHandled,
+ nsecs_t inConsumeTime) -> void {
+ publishedSeq = inSeq;
+ handled = inHandled;
+ };
+ status_t status = mInputPublisher.receiveFinishedSignal(callback);
if (status) {
if (status == WOULD_BLOCK) {
return OK;
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 191f748..0aac07d 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -51,19 +51,12 @@
client->decStrong((void*)nativeCreate);
}
-static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
- SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
- client->dispose();
-}
-
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "nativeCreate", "()J",
(void*)nativeCreate },
{ "nativeDestroy", "(J)V",
(void*)nativeDestroy },
- { "nativeKill", "(J)V",
- (void*)nativeKill }
};
int register_android_view_SurfaceSession(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 856657a..99014c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
android:description="@string/permdesc_accessImsCallService"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+ Only granted if the application is a system app AND is in the Default SMS Role.
+ The permission is revoked when the app is taken out of the Default SMS Role.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to read the user's call log.
<p class="note"><strong>Note:</strong> If your app uses the
{@link #READ_CONTACTS} permission and <em>both</em> your <a
@@ -3994,6 +4002,11 @@
<permission android:name="android.permission.MOVE_PACKAGE"
android:protectionLevel="signature|privileged" />
+ <!-- @TestApi Allows an application to keep uninstalled packages as apks.
+ @hide -->
+ <permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to change whether an application component (other than its own) is
enabled or not.
<p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 586c99d7..aeb4fc468 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1140,6 +1140,15 @@
<!-- The color applied to the edge effect on scrolling containers. -->
<attr name="colorEdgeEffect" format="color" />
+ <!-- The type of the edge effect. The default is glow. -->
+ <attr name="edgeEffectType">
+ <!-- Use a colored glow at the edge. -->
+ <enum name="glow" value="0" />
+
+ <!-- Stretch the content. -->
+ <enum name="stretch" value="1" />
+ </attr>
+
<!-- =================== -->
<!-- Lighting properties -->
<!-- =================== -->
@@ -9050,6 +9059,7 @@
<!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
<declare-styleable name="EdgeEffect">
<attr name="colorEdgeEffect" />
+ <attr name="edgeEffectType" />
</declare-styleable>
<!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580a..0ae6a76 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
<flag name="recents" value="0x2000000" />
<!-- Additional flag from base permission type: this permission is managed by role. -->
<flag name="role" value="0x4000000" />
+ <!-- Additional flag from base permission type: this permission can also be granted if the
+ requesting application is signed by, or has in its signing lineage, any of the
+ certificate digests declared in {@link android.R.attr#knownCerts}. -->
+ <flag name="knownSigner" value="0x8000000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
@@ -364,6 +368,15 @@
{@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
<attr name="permissionGroup" format="string" />
+ <!-- A reference to an array resource containing the signing certificate digests to be granted
+ this permission when using the {@code knownSigner} protection flag. The digest should
+ be computed over the DER encoding of the trusted certificate using the SHA-256 digest
+ algorithm.
+ <p>
+ If only a single signer is declared this can also be a string resource, or the digest
+ can be declared inline as the value for this attribute. -->
+ <attr name="knownCerts" format="reference|string" />
+
<!-- Specify the name of a user ID that will be shared between multiple
packages. By default, each package gets its own unique user-id.
By setting this value on two or more packages, each of these packages
@@ -1935,6 +1948,7 @@
<attr name="request" />
<attr name="protectionLevel" />
<attr name="permissionFlags" />
+ <attr name="knownCerts" />
</declare-styleable>
<!-- The <code>permission-group</code> tag declares a logical grouping of
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index beae935..b7c755e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1590,8 +1590,8 @@
take precedence over lower ones.
See com.android.server.timedetector.TimeDetectorStrategy for available sources. -->
<string-array name="config_autoTimeSourcesPriority">
- <item>telephony</item>
<item>network</item>
+ <item>telephony</item>
</string-array>
<!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
@@ -1946,8 +1946,6 @@
<string name="config_systemAutomotiveProjection" translatable="false"></string>
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false"></string>
- <!-- The name of the package that will hold the system video call role. -->
- <string name="config_systemVideoCall" translatable="false"></string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d3f3ebd..068987e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3063,6 +3063,8 @@
<public name="hotwordDetectionService" />
<public name="previewLayout" />
<public name="clipToOutline" />
+ <public name="edgeEffectType" />
+ <public name="knownCerts" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3117,8 +3119,6 @@
<!-- @hide @SystemApi @TestApi -->
<public name="config_systemAutomotiveCluster" />
<!-- @hide @SystemApi @TestApi -->
- <public name="config_systemVideoCall" />
- <!-- @hide @SystemApi @TestApi -->
<public name="config_systemAutomotiveProjection" />
</public-group>
diff --git a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
similarity index 98%
rename from core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 6bcec25..266ff3d 100644
--- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package android.app.time;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
new file mode 100644
index 0000000..606e81d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionInfoTest {
+ private static final String KNOWN_CERT_DIGEST_1 =
+ "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+ private static final String KNOWN_CERT_DIGEST_2 =
+ "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6";
+
+ @Test
+ public void createFromParcel_returnsKnownCerts() {
+ // The platform supports a knownSigner permission protection flag that allows one or more
+ // trusted signing certificates to be specified with the permission declaration; if a
+ // requesting app is signed by any of these trusted certificates the permission is granted.
+ // This test verifies the Set of knownCerts is properly parceled / unparceled.
+ PermissionInfo permissionInfo = new PermissionInfo();
+ permissionInfo.protectionLevel =
+ PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+ permissionInfo.knownCerts = new ArraySet<>(2);
+ permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1);
+ permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2);
+ Parcel parcel = Parcel.obtain();
+ permissionInfo.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel);
+
+ assertNotNull(unparceledPermissionInfo.knownCerts);
+ assertEquals(2, unparceledPermissionInfo.knownCerts.size());
+ assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1));
+ assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2));
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index bffd1e4..49b720c 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -29,6 +29,7 @@
import android.content.pm.PackageParser.SigningDetails;
import android.util.ArraySet;
+import android.util.PackageUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -817,6 +818,124 @@
assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
}
+ @Test
+ public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
+ // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
+ // is currently signed, or has previously been signed, by any of the certificate digests
+ // in the provided Set. This test verifies if a null Set is provided then false is returned.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(null));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
+ // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
+ // instance of the SigningDetails then false is returned.
+ SigningDetails details = SigningDetails.UNKNOWN;
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
+ // If the single signer of an app is in the provided digest Set then
+ // the method should return true.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
+ // If the single signer of an app is not in the provided digest Set then
+ // the method should return false.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
+ // If an app is signed by multiple signers and all of the signers are in
+ // the digest Set then the method should return true.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
+ throws Exception {
+ // If an app is signed by multiple signers then all signers must be in the digest Set; if
+ // only a subset of the signers are in the Set then the method should return false.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
+ throws Exception {
+ // If an app is signed by multiple signers and the Set size is smaller than the number of
+ // signers then the method should immediately return false since there's no way for the
+ // requirement of all signers in the Set to be met.
+ SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
+ // If an app has a rotated signing key and a previous key in the lineage is in the digest
+ // Set then this method should return true.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
+ // If an app has a rotated signing key, but neither the current key nor any of the signers
+ // in the lineage are in the digest set then the method should return false.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
+ throws Exception {
+ // If an app has multiple signers in the lineage only one of those signers must be in the
+ // Set for this method to return true. This test verifies if the last signer in the lineage
+ // is in the set then the method returns true.
+ SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE);
+
+ assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
+ @Test
+ public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
+ throws Exception {
+ // Under some instances an app with only a single signer can have a null lineage; this
+ // test verifies that null lineage does not result in a NullPointerException and instead the
+ // method returns false if the single signer is not in the Set.
+ SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
+ Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+ }
+
private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
int[] capabilities = new int[signers.length];
for (int i = 0; i < capabilities.length; i++) {
@@ -853,12 +972,21 @@
// If there are multiple signers then the pastSigningCertificates should be set to null, but
// if there is only a single signer both the current signer and the past signers should be
// set to that one signer.
- if (signers.length > 1) {
+ if (signers.length > 1 || useNullPastSigners) {
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
}
return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
}
+ private Set<String> createDigestSet(String... signers) {
+ Set<String> digests = new ArraySet<>();
+ for (String signer : signers) {
+ String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
+ digests.add(digest);
+ }
+ return digests;
+ }
+
private void assertSigningDetailsContainsLineage(SigningDetails details,
String... pastSigners) {
// This method should only be invoked for results that contain a single signer.
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 564103e..11239db 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -17,6 +17,8 @@
package android.os;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
@@ -31,7 +33,6 @@
@Presubmit
@RunWith(JUnit4.class)
public class CombinedVibrationEffectTest {
-
private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
@@ -172,6 +173,37 @@
}
@Test
+ public void testHasVibratorMono_returnsTrueForAnyVibrator() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ assertTrue(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ }
+
+ @Test
+ public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+ .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .combine();
+ assertFalse(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ assertFalse(effect.hasVibrator(2));
+ }
+
+ @Test
+ public void testHasVibratorSequential_returnsNestedVibrators() {
+ CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+ .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addNext(CombinedVibrationEffect.startSynced()
+ .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+ .combine())
+ .combine();
+ assertFalse(effect.hasVibrator(0));
+ assertTrue(effect.hasVibrator(1));
+ assertTrue(effect.hasVibrator(2));
+ }
+
+ @Test
public void testSerializationMono() {
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
index a43b238..a121941 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
@@ -16,14 +16,14 @@
package android.service.notification;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static com.google.common.truth.Truth.assertThat;
-import android.app.NotificationChannel;
+import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.util.ArraySet;
@@ -45,16 +45,19 @@
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS
| FLAG_FILTER_TYPE_ALERTING
- | FLAG_FILTER_TYPE_SILENT);
+ | FLAG_FILTER_TYPE_SILENT
+ | FLAG_FILTER_TYPE_ONGOING);
assertThat(nlf.getDisallowedPackages()).isEmpty();
- assertThat(nlf.isPackageAllowed("pkg1")).isTrue();
+ assertThat(nlf.isPackageAllowed(new VersionedPackage("any", 0))).isTrue();
}
@Test
public void testConstructor() {
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
@@ -62,20 +65,21 @@
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
- assertThat(nlf.getDisallowedPackages()).contains("pkg1");
- assertThat(nlf.getDisallowedPackages()).contains("pkg2");
- assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
- assertThat(nlf.isPackageAllowed("pkg2")).isFalse();
+ assertThat(nlf.getDisallowedPackages()).contains(a1);
+ assertThat(nlf.getDisallowedPackages()).contains(a2);
+ assertThat(nlf.isPackageAllowed(a1)).isFalse();
+ assertThat(nlf.isPackageAllowed(a2)).isFalse();
}
@Test
public void testSetDisallowedPackages() {
NotificationListenerFilter nlf = new NotificationListenerFilter();
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"});
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(
+ new VersionedPackage[] {new VersionedPackage("pkg1", 0)});
nlf.setDisallowedPackages(pkgs);
- assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
+ assertThat(nlf.isPackageAllowed(new VersionedPackage("pkg1", 0))).isFalse();
}
@Test
@@ -94,7 +98,9 @@
@Test
public void testDescribeContents() {
final int expected = 0;
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.describeContents()).isEqualTo(expected);
@@ -102,7 +108,9 @@
@Test
public void testParceling() {
- ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+ ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
@@ -116,9 +124,9 @@
assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
- assertThat(nlf1.getDisallowedPackages()).contains("pkg1");
- assertThat(nlf1.getDisallowedPackages()).contains("pkg2");
- assertThat(nlf1.isPackageAllowed("pkg1")).isFalse();
- assertThat(nlf1.isPackageAllowed("pkg2")).isFalse();
+ assertThat(nlf1.getDisallowedPackages()).contains(a1);
+ assertThat(nlf1.getDisallowedPackages()).contains(a2);
+ assertThat(nlf1.isPackageAllowed(a1)).isFalse();
+ assertThat(nlf1.isPackageAllowed(a2)).isFalse();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index d2107ea..08205b4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -218,7 +218,7 @@
sippers.add(sipperBg);
sippers.add(sipperFg);
- spc.smearScreenBatterySipper(sippers, mScreenBatterySipper);
+ spc.smearScreenBatterySipper(sippers, mScreenBatterySipper, 0);
assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0);
assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0);
@@ -253,7 +253,7 @@
doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
anyLong(), anyInt());
- final long time = spc.getProcessForegroundTimeMs(mUid);
+ final long time = spc.getProcessForegroundTimeMs(mUid, 1000);
assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
}
@@ -296,7 +296,7 @@
doReturn(uidCode).when(sipper).getUid();
if (!isUidNull) {
final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
- doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid));
+ doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid), anyLong());
doReturn(uidCode).when(uid).getUid();
sipper.uidObj = uid;
}
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 97c07ea..6652c64 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
import android.os.WorkSource;
+import android.util.SparseLongArray;
import android.view.Display;
import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
}
+ @SmallTest
+ public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setOnBatteryInternal(true);
+
+ final int uid1 = 11500;
+ final int uid2 = 11501;
+
+ // Initially, all custom buckets report energy of 0.
+ checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+ }
+
+ @SmallTest
+ public void testUpdateCustomMeasuredEnergyDataLocked() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ final int bucketA = 0; // Custom bucket 0
+ final int bucketB = 1; // Custom bucket 1
+
+ long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+ long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+ final int uid1 = 10500;
+ long blame1A = 0; // Blame for uid1 in bucketA
+ long blame1B = 0; // Blame for uid1 in bucketB
+
+ final int uid2 = 10501;
+ long blame2A = 0; // Blame for uid2 in bucketA
+ long blame2B = 0; // Blame for uid2 in bucketB
+
+ final SparseLongArray newEnergiesA = new SparseLongArray(2);
+ final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+ // ----- Case A: battery off (so blame does not increase)
+ bi.setOnBatteryInternal(false);
+
+ newEnergiesA.put(uid1, 20_000);
+ // Implicit newEnergiesA.put(uid2, 0);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+ newEnergiesB.put(uid1, 60_000);
+ // Implicit newEnergiesB.put(uid2, 0);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+ checkCustomMeasuredEnergy(
+ "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case B: battery on
+ bi.setOnBatteryInternal(true);
+
+ newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+ // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+ totalBlameA += 310_000;
+
+ newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+ newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+ totalBlameB += 790_000;
+
+ checkCustomMeasuredEnergy(
+ "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case C: battery still on
+ newEnergiesA.delete(uid1); blame1A += 0;
+ newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+ totalBlameA += 560_000;
+
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+ totalBlameB += 10_000;
+
+ checkCustomMeasuredEnergy(
+ "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+ // ----- Case D: battery still on
+ bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+ bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+ totalBlameB += 15_000;
+ checkCustomMeasuredEnergy(
+ "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
@@ -610,4 +700,33 @@
assertEquals("Wrong doze for Case " + caseName, globalDoze,
bi.getScreenDozeEnergy());
}
+
+ private void checkCustomMeasuredEnergy(String caseName,
+ long totalBlameA, long totalBlameB,
+ int uid1, long blame1A, long blame1B,
+ int uid2, long blame2A, long blame2B,
+ MockBatteryStatsImpl bi) {
+
+ final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules();
+ final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules();
+ final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules();
+
+ assertNotNull(actualTotal);
+ assertNotNull(actualUid1);
+ assertNotNull(actualUid2);
+
+ assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+ actualTotal[0]);
+
+ assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+ actualTotal[1]);
+
+ assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]);
+
+ assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]);
+
+ assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]);
+
+ assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index fdfc7ac..d50bb05 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -232,7 +232,7 @@
assertThat(entry3.handlerClassName).isEqualTo(
"com.android.internal.os.LooperStatsTest$TestHandlerSecond");
assertThat(entry3.messageName).startsWith(
- "com.android.internal.os.-$$Lambda$LooperStatsTest$");
+ "com.android.internal.os.LooperStatsTest-$$ExternalSyntheticLambda");
assertThat(entry3.messageCount).isEqualTo(1);
assertThat(entry3.recordedMessageCount).isEqualTo(1);
assertThat(entry3.exceptionCount).isEqualTo(0);
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index e43caa3..717fac0 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -18,8 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityManager;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
import android.view.Display;
import androidx.test.filters.SmallTest;
@@ -33,20 +37,37 @@
@SmallTest
public class ScreenPowerCalculatorTest {
private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
+ private static final long MINUTE_IN_MS = 60 * 1000;
+ private static final long MINUTE_IN_US = 60 * 1000 * 1000;
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePower(PowerProfile.POWER_SCREEN_ON, 360.0)
- .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 3600.0);
+ .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0)
+ .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0);
@Test
- public void testTimerBasedModel() {
- BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+ public void testEnergyBasedModel() {
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- stats.noteScreenStateLocked(Display.STATE_ON, 1000, 1000, 1000);
- stats.noteScreenBrightnessLocked(100, 1000, 1000);
- stats.noteScreenBrightnessLocked(200, 2000, 2000);
- stats.noteScreenStateLocked(Display.STATE_OFF, 3000, 3000, 3000);
+ batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+ batteryStats.updateDisplayEnergyLocked(0, Display.STATE_ON, 2 * MINUTE_IN_MS);
+
+ setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+ setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+ batteryStats.updateDisplayEnergyLocked(300_000_000, Display.STATE_ON,
+ 60 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+ batteryStats.updateDisplayEnergyLocked(100_000_000, Display.STATE_DOZE,
+ 120 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
ScreenPowerCalculator calculator =
new ScreenPowerCalculator(mStatsRule.getPowerProfile());
@@ -56,8 +77,79 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
- .isEqualTo(2000);
+ .isEqualTo(80 * MINUTE_IN_MS);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
- .isWithin(PRECISION).of(1.2);
+ .isWithin(PRECISION).of(30.03003);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+ .isEqualTo(18 * MINUTE_IN_MS);
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(8.44594);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(21.58408);
+ }
+
+ @Test
+ public void testPowerProfileBasedModel() {
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+
+ setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+ setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+ setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ .isEqualTo(80 * MINUTE_IN_MS);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ .isWithin(PRECISION).of(88.4);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+ .isEqualTo(18 * MINUTE_IN_MS);
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(14.73333);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(73.66666);
+ }
+
+ private void setFgState(int uid, boolean fgOn, long realtimeMs, long uptimeMs) {
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ if (fgOn) {
+ batteryStats.noteActivityResumedLocked(uid, realtimeMs, uptimeMs);
+ batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_TOP,
+ realtimeMs, uptimeMs);
+ } else {
+ batteryStats.noteActivityPausedLocked(uid, realtimeMs, uptimeMs);
+ batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+ realtimeMs, uptimeMs);
+ }
}
}
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 b9908f4..5fd5a78 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,59 @@
}
@Test
+ public void testIsValidCustomBucket() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+ assertFalse(stats.isValidCustomBucket(-1));
+ assertTrue(stats.isValidCustomBucket(0));
+ assertTrue(stats.isValidCustomBucket(1));
+ assertTrue(stats.isValidCustomBucket(2));
+ assertFalse(stats.isValidCustomBucket(3));
+ assertFalse(stats.isValidCustomBucket(4));
+
+ final MeasuredEnergyStats boringStats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+ assertFalse(boringStats.isValidCustomBucket(-1));
+ assertFalse(boringStats.isValidCustomBucket(0));
+ assertFalse(boringStats.isValidCustomBucket(1));
+ }
+
+ @Test
+ public void testGetAccumulatedCustomBucketEnergies() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+
+ stats.updateCustomBucket(0, 50, true);
+ stats.updateCustomBucket(1, 60, true);
+ stats.updateCustomBucket(2, 13, true);
+ stats.updateCustomBucket(1, 70, true);
+
+ final long[] output = stats.getAccumulatedCustomBucketEnergies();
+ assertEquals(3, output.length);
+
+ assertEquals(50, output[0]);
+ assertEquals(60 + 70, output[1]);
+ assertEquals(13, output[2]);
+ }
+
+ @Test
+ public void testGetAccumulatedCustomBucketEnergies_empty() {
+ final MeasuredEnergyStats stats
+ = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+
+ final long[] output = stats.getAccumulatedCustomBucketEnergies();
+ assertEquals(0, output.length);
+ }
+
+ @Test
+ public void testGetNumberCustomEnergyBuckets() {
+ assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0)
+ .getNumberCustomEnergyBuckets());
+ assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3)
+ .getNumberCustomEnergyBuckets());
+ }
+
+ @Test
public void testReset() {
final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
final int numCustomBuckets = 2;
diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..b40fe24
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS
@@ -0,0 +1,3 @@
+# LockSettings related
+per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/tests/overlaytests/device/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml
index e918268..a30d66f 100644
--- a/core/tests/overlaytests/device/res/values/config.xml
+++ b/core/tests/overlaytests/device/res/values/config.xml
@@ -2,6 +2,7 @@
<resources>
<string name="str">none</string>
<string name="str2">none</string>
+ <integer name="overlaid">0</integer>
<integer name="matrix_100000">100</integer>
<integer name="matrix_100001">100</integer>
<integer name="matrix_100010">100</integer>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
new file mode 100644
index 0000000..3465989
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.overlaytest;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public class FabricatedOverlaysTest {
+ private static final String TAG = "FabricatedOverlaysTest";
+ private final String TEST_RESOURCE = "integer/overlaid";
+ private final String TEST_OVERLAY_NAME = "Test";
+
+ private Context mContext;
+ private Resources mResources;
+ private OverlayManager mOverlayManager;
+ private int mUserId;
+ private UserHandle mUserHandle;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mResources = mContext.getResources();
+ mOverlayManager = mContext.getSystemService(OverlayManager.class);
+ mUserId = UserHandle.myUserId();
+ mUserHandle = UserHandle.of(mUserId);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+ mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+ info -> {
+ if (info.isFabricated()) {
+ cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+ }
+ });
+ mOverlayManager.commit(cleanUp.build());
+ }
+
+ @Test
+ public void testFabricatedOverlay() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build());
+
+ OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+ assertNotNull(info);
+ assertFalse(info.isEnabled());
+
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build());
+
+ info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+ assertNotNull(info);
+ assertTrue(info.isEnabled());
+
+ waitForResourceValue(1);
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .unregisterFabricatedOverlay(overlay.getIdentifier())
+ .build());
+
+ waitForResourceValue(0);
+ }
+
+ @Test
+ public void testRegisterEnableAtomic() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build());
+
+ waitForResourceValue(1);
+ }
+
+ @Test
+ public void testRegisterTwice() throws Exception {
+ FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build());
+
+ waitForResourceValue(1);
+ overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 2)
+ .build();
+
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build());
+ waitForResourceValue(2);
+ }
+
+ @Test
+ public void testInvalidOwningPackageName() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ assertThrows(SecurityException.class, () ->
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build()));
+
+ assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+
+ @Test
+ public void testInvalidOverlayName() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), "invalid@name", mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ assertThrows(SecurityException.class, () ->
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build()));
+
+ assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+
+ @Test
+ public void testOverlayIdentifierLongest() throws Exception {
+ final int maxLength = 255 - 11; // 11 reserved characters
+ final String longestName = String.join("",
+ Collections.nCopies(maxLength - mContext.getPackageName().length(), "a"));
+ {
+ FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+ longestName, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build());
+ assertNotNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+ {
+ FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+ longestName + "a", mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ assertThrows(SecurityException.class, () ->
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build()));
+
+ assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+ }
+
+ @Test
+ public void testInvalidResourceValues() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ assertThrows(SecurityException.class, () ->
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .build()));
+
+ assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+
+ @Test
+ public void testTransactionFailRollback() throws Exception {
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+ .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+ .build();
+
+ waitForResourceValue(0);
+ assertThrows(SecurityException.class, () ->
+ mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setEnabled(overlay.getIdentifier(), true, mUserId)
+ .setEnabled(new OverlayIdentifier("not-valid"), true, mUserId)
+ .build()));
+
+ assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+ }
+
+ void waitForResourceValue(final int expectedValue) throws TimeoutException {
+ final long timeOutDuration = 10000;
+ final long endTime = System.currentTimeMillis() + timeOutDuration;
+ final String resourceName = TEST_RESOURCE;
+ final int resourceId = mResources.getIdentifier(resourceName, "",
+ mContext.getPackageName());
+ int resourceValue = 0;
+ while (System.currentTimeMillis() < endTime) {
+ resourceValue = mResources.getInteger(resourceId);
+ if (resourceValue == expectedValue) {
+ return;
+ }
+ }
+ final String paths = TextUtils.join(",", mResources.getAssets().getApkPaths());
+ Log.w(TAG, "current paths: [" + paths + "]", new Throwable());
+ throw new TimeoutException("Timed out waiting for '" + resourceName + "' value to equal '"
+ + expectedValue + "': current value is '" + resourceValue + "'");
+ }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
index 76c01a7..3c0c131 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
@@ -32,14 +33,14 @@
class LocalOverlayManager {
private static final long TIMEOUT = 30;
- public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable,
- @NonNull final String[] overlaysToDisable) throws Exception {
+ public static void toggleOverlaysAndWait(@NonNull final OverlayIdentifier[] overlaysToEnable,
+ @NonNull final OverlayIdentifier[] overlaysToDisable) throws Exception {
final int userId = UserHandle.myUserId();
OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
- for (String pkg : overlaysToEnable) {
+ for (OverlayIdentifier pkg : overlaysToEnable) {
builder.setEnabled(pkg, true, userId);
}
- for (String pkg : overlaysToDisable) {
+ for (OverlayIdentifier pkg : overlaysToDisable) {
builder.setEnabled(pkg, false, userId);
}
OverlayManagerTransaction transaction = builder.build();
@@ -48,7 +49,7 @@
FutureTask<Boolean> task = new FutureTask<>(() -> {
while (true) {
final String[] paths = ctx.getResources().getAssets().getApkPaths();
- if (arrayTailContains(paths, overlaysToEnable)
+ if (arrayTailContainsOverlays(paths, overlaysToEnable)
&& arrayDoesNotContain(paths, overlaysToDisable)) {
return true;
}
@@ -64,15 +65,15 @@
task.get(TIMEOUT, SECONDS);
}
- private static boolean arrayTailContains(@NonNull final String[] array,
- @NonNull final String[] substrings) {
- if (array.length < substrings.length) {
+ private static boolean arrayTailContainsOverlays(@NonNull final String[] array,
+ @NonNull final OverlayIdentifier[] overlays) {
+ if (array.length < overlays.length) {
return false;
}
- for (int i = 0; i < substrings.length; i++) {
- String a = array[array.length - substrings.length + i];
- String s = substrings[i];
- if (!a.contains(s)) {
+ for (int i = 0; i < overlays.length; i++) {
+ String a = array[array.length - overlays.length + i];
+ OverlayIdentifier s = overlays[i];
+ if (!a.contains(s.getPackageName())) {
return false;
}
}
@@ -80,10 +81,10 @@
}
private static boolean arrayDoesNotContain(@NonNull final String[] array,
- @NonNull final String[] substrings) {
- for (String s : substrings) {
+ @NonNull final OverlayIdentifier[] overlays) {
+ for (OverlayIdentifier s : overlays) {
for (String a : array) {
- if (a.contains(s)) {
+ if (a.contains(s.getPackageName())) {
return false;
}
}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index 636f4c8..8e4b9ef 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.fail;
import android.content.Context;
+import android.content.om.OverlayIdentifier;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -58,9 +59,12 @@
static final int MODE_SINGLE_OVERLAY = 1;
static final int MODE_MULTIPLE_OVERLAYS = 2;
- static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
- static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
- static final String FRAMEWORK_OVERLAY_PKG = "com.android.overlaytest.framework";
+ static final OverlayIdentifier APP_OVERLAY_ONE_PKG =
+ new OverlayIdentifier("com.android.overlaytest.app_overlay_one");
+ static final OverlayIdentifier APP_OVERLAY_TWO_PKG =
+ new OverlayIdentifier("com.android.overlaytest.app_overlay_two");
+ static final OverlayIdentifier FRAMEWORK_OVERLAY_PKG =
+ new OverlayIdentifier("com.android.overlaytest.framework");
protected OverlayBaseTest(int mode) {
mMode = mode;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
index 0b4f5e2..27d7342 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
@@ -16,14 +16,19 @@
package com.android.overlaytest;
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_ONE_PKG;
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_TWO_PKG;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.testng.Assert.assertThrows;
import android.content.Context;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
import android.content.om.OverlayManagerTransaction;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.UserHandle;
@@ -40,8 +45,6 @@
@RunWith(JUnit4.class)
@MediumTest
public class TransactionTest {
- static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
- static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
private Context mContext;
private Resources mResources;
@@ -58,8 +61,8 @@
mUserHandle = UserHandle.of(mUserId);
LocalOverlayManager.toggleOverlaysAndWait(
- new String[]{},
- new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+ new OverlayIdentifier[]{},
+ new OverlayIdentifier[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
}
@Test
@@ -78,8 +81,8 @@
List<OverlayInfo> ois =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois.size(), 2);
- assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG);
- assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG);
+ assertEquals(ois.get(0).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
+ assertEquals(ois.get(1).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_TWO_PKG, true)
@@ -92,8 +95,8 @@
List<OverlayInfo> ois2 =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois2.size(), 2);
- assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG);
- assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG);
+ assertEquals(ois2.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+ assertEquals(ois2.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_TWO_PKG, false)
@@ -105,8 +108,8 @@
List<OverlayInfo> ois3 =
mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
assertEquals(ois3.size(), 2);
- assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG);
- assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG);
+ assertEquals(ois3.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+ assertEquals(ois3.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
}
@Test
@@ -116,7 +119,7 @@
OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
.setEnabled(APP_OVERLAY_ONE_PKG, true)
- .setEnabled("does-not-exist", true)
+ .setEnabled(new OverlayIdentifier("does-not-exist"), true)
.setEnabled(APP_OVERLAY_TWO_PKG, true)
.build();
assertThrows(SecurityException.class, () -> mOverlayManager.commit(t));
@@ -125,9 +128,10 @@
assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
}
- private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) {
- final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId));
+ private void assertOverlayIsEnabled(final OverlayIdentifier overlay, boolean enabled,
+ int userId) {
+ final OverlayInfo oi = mOverlayManager.getOverlayInfo(overlay, UserHandle.of(userId));
assertNotNull(oi);
- assertEquals(oi.isEnabled(), enabled);
+ assertEquals(enabled, oi.isEnabled());
}
}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index 420f755..5587203 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -16,6 +16,8 @@
package com.android.overlaytest;
+import android.content.om.OverlayIdentifier;
+
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
@BeforeClass
public static void enableOverlay() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
- new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG},
- new String[]{});
+ new OverlayIdentifier[]{
+ FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+ },
+ new OverlayIdentifier[]{});
}
}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index a86255e..d275433 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -16,6 +16,8 @@
package com.android.overlaytest;
+import android.content.om.OverlayIdentifier;
+
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,7 @@
@BeforeClass
public static void enableOverlays() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
- new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
- new String[]{APP_OVERLAY_TWO_PKG});
+ new OverlayIdentifier[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
+ new OverlayIdentifier[]{APP_OVERLAY_TWO_PKG});
}
}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index 51c4118..72cba8b 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -16,6 +16,8 @@
package com.android.overlaytest;
+import android.content.om.OverlayIdentifier;
+
import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
@BeforeClass
public static void disableOverlays() throws Exception {
LocalOverlayManager.toggleOverlaysAndWait(
- new String[]{},
- new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+ new OverlayIdentifier[]{},
+ new OverlayIdentifier[]{
+ FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+ });
}
}
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
index 38e5fa1..926b186 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
@@ -25,7 +25,6 @@
<item target="integer/matrix_100100" value="@integer/matrix_100100"/>
<item target="integer/matrix_100101" value="@integer/matrix_100101"/>
<item target="integer/matrix_100110" value="@integer/matrix_100110"/>
- <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
<item target="integer/matrix_100111" value="@integer/matrix_100111"/>
<item target="integer/matrix_101000" value="@integer/matrix_101000"/>
<item target="integer/matrix_101001" value="@integer/matrix_101001"/>
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 65d3a01..549e074 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -6,7 +6,6 @@
jsharkey@android.com
jsharkey@google.com
lorenzo@google.com
-moltmann@google.com
svetoslavganov@android.com
svetoslavganov@google.com
toddke@android.com
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index cf0199b..c940574 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -26,6 +26,7 @@
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.FORCE_STOP_PACKAGES"/>
+ <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -40,10 +41,12 @@
<permission name="android.permission.MOVE_PACKAGE"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
+ <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
<permission name="android.permission.SET_TIME"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/car/com.android.car.provision.xml b/data/etc/car/com.android.car.provision.xml
index 4fd9cae..42cfd3c 100644
--- a/data/etc/car/com.android.car.provision.xml
+++ b/data/etc/car/com.android.car.provision.xml
@@ -17,6 +17,7 @@
<permissions>
<privapp-permissions package="com.android.car.provision">
<permission name="android.car.permission.CAR_POWERTRAIN"/>
+ <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.MANAGE_USERS"/>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index 19548bc..48f6ab3 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -25,6 +25,7 @@
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.READ_LOGS"/>
+ <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 3a20a9c..bd30d7a 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -27,8 +27,10 @@
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
+ <permission name="android.permission.LOCK_DEVICE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MASTER_CLEAR"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
@@ -40,9 +42,11 @@
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.RESET_PASSWORD"/>
<permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
<!-- use for CarServiceTest -->
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
+ <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 734561c..fa92b6d 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,6 +19,7 @@
<!-- Required to place emergency calls from emergency info screen. -->
<permission name="android.permission.CALL_PRIVILEGED"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml
index d2ea0ec..68f8298 100644
--- a/data/etc/com.android.provision.xml
+++ b/data/etc/com.android.provision.xml
@@ -17,7 +17,7 @@
<permissions>
<privapp-permissions package="com.android.provision">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
- <permissionn ame="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+ <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
<permission name="android.permission.MASTER_CLEAR"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e473c55..3fdb0da 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -58,5 +58,6 @@
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_DREAM_SUPPRESSION"/>
+ <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index cf2f970..25f76f6 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -96,6 +97,27 @@
}
/**
+ * @return Returns a {@link String} that represents this point which can be parsed with
+ * {@link #unflattenFromString(String)}.
+ * @hide
+ */
+ @NonNull
+ public String flattenToString() {
+ return x + "x" + y;
+ }
+
+ /**
+ * @return Returns a {@link Point} from a short string created from {@link #flattenToString()}.
+ * @hide
+ */
+ @Nullable
+ public static Point unflattenFromString(String s) throws NumberFormatException {
+ final int sep_ix = s.indexOf("x");
+ return new Point(Integer.parseInt(s.substring(0, sep_ix)),
+ Integer.parseInt(s.substring(sep_ix + 1)));
+ }
+
+ /**
* Parcelable interface methods
*/
@Override
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..40c75a4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1414,6 +1414,16 @@
return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
}
+ /** @hide */
+ public List<FontFamily> getFallback() {
+ ArrayList<FontFamily> families = new ArrayList<>();
+ int familySize = nativeGetFamilySize(native_instance);
+ for (int i = 0; i < familySize; ++i) {
+ families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+ }
+ return families;
+ }
+
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
@@ -1439,6 +1449,13 @@
@CriticalNative
private static native long nativeGetReleaseFunc();
+ @CriticalNative
+ private static native int nativeGetFamilySize(long naitvePtr);
+
+ @CriticalNative
+ private static native long nativeGetFamily(long nativePtr, int index);
+
+
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
private static native int nativeWriteTypefaces(
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index b153c99..7b67ca9 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -63,6 +63,10 @@
NativeAllocationRegistry.createMalloced(
ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont());
+ private static final NativeAllocationRegistry FONT_REGISTRY =
+ NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
+ nGetReleaseNativeFont());
+
private static final Object SOURCE_ID_LOCK = new Object();
@GuardedBy("SOURCE_ID_LOCK")
private static final LongSparseLongArray FONT_SOURCE_ID_MAP =
@@ -526,11 +530,14 @@
* Use Builder instead
*
* Caller must increment underlying minikin::Font ref count.
+ * This class takes the ownership of the passing native objects.
*
* @hide
*/
public Font(long nativePtr) {
mNativePtr = nativePtr;
+
+ FONT_REGISTRY.registerNativeAllocation(this, mNativePtr);
}
/**
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
nAddFont(builderPtr, mFonts.get(i).getNativePtr());
}
final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
- final FontFamily family = new FontFamily(mFonts, ptr);
+ final FontFamily family = new FontFamily(ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
}
@@ -146,7 +146,8 @@
private final long mNativePtr;
// Use Builder instead.
- private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+ /** @hide */
+ public FontFamily(long ptr) {
mNativePtr = ptr;
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
*/
public static @NonNull Set<Font> getAvailableFonts() {
synchronized (LOCK) {
- if (sAvailableFonts != null) {
- return sAvailableFonts;
- }
-
- if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
- sAvailableFonts = collectAllFonts();
- } else {
+ if (sAvailableFonts == null) {
Set<Font> set = new ArraySet<>();
- for (FontFamily[] items : sFamilyMap.values()) {
- for (FontFamily family : items) {
- for (int i = 0; i < family.getSize(); ++i) {
- set.add(family.getFont(i));
+ for (Typeface tf : Typeface.getSystemFontMap().values()) {
+ List<FontFamily> families = tf.getFallback();
+ for (int i = 0; i < families.size(); ++i) {
+ FontFamily family = families.get(i);
+ for (int j = 0; j < family.getSize(); ++j) {
+ set.add(family.getFont(j));
}
}
}
-
sAvailableFonts = Collections.unmodifiableSet(set);
}
return sAvailableFonts;
}
}
- private static @NonNull Set<Font> collectAllFonts() {
- // TODO: use updated fonts
- FontConfig fontConfig = getSystemPreinstalledFontConfig();
- Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
- Set<Font> res = new ArraySet<>();
- for (FontFamily[] families : map.values()) {
- for (FontFamily family : families) {
- for (int i = 0; i < family.getSize(); ++i) {
- res.add(family.getFont(i));
- }
- }
- }
- return res;
- }
-
private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
try (FileInputStream file = new FileInputStream(fullPath)) {
final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
-
- /**
- * @hide
- */
- public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
- synchronized (LOCK) {
- sFamilyMap = fallbackMap;
- }
- }
}
diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS
index f04e200..057dc0d 100644
--- a/graphics/java/android/graphics/pdf/OWNERS
+++ b/graphics/java/android/graphics/pdf/OWNERS
@@ -5,4 +5,3 @@
sumir@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-moltmann@google.com
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 334b111..988838b 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -17,6 +17,7 @@
package android.security.keystore;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Build;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
@@ -25,6 +26,8 @@
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -477,11 +480,11 @@
success = true;
return keyPair;
- } catch (ProviderException e) {
+ } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) {
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(e);
} else {
- throw e;
+ throw new ProviderException(e);
}
} finally {
if (!success) {
@@ -491,7 +494,7 @@
}
private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
- throws ProviderException {
+ throws ProviderException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
KeymasterArguments args = new KeymasterArguments();
@@ -510,6 +513,60 @@
Build.MODEL.getBytes(StandardCharsets.UTF_8));
}
+ int[] idTypes = mSpec.getAttestationIds();
+ if (idTypes != null) {
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+ || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+ telephonyService =
+ (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException(
+ "Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case AttestationUtils.ID_TYPE_SERIAL:
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ case AttestationUtils.ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ }
+ case AttestationUtils.ID_TYPE_MEID: {
+ final String meid = telephonyService.getMeid(0);
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8)
+ );
+ break;
+ }
+ case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+ args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
+ }
+ }
+
return getAttestationChain(privateKeyAlias, keyPair, args);
}
@@ -547,7 +604,8 @@
}
}
- private KeymasterArguments constructKeyGenerationArguments() {
+ private KeymasterArguments constructKeyGenerationArguments()
+ throws IllegalArgumentException, DeviceIdAttestationException {
KeymasterArguments args = new KeymasterArguments();
args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -565,9 +623,9 @@
mSpec.getKeyValidityForConsumptionEnd());
addAlgorithmSpecificParameters(args);
- if (mSpec.isUniqueIdIncluded())
+ if (mSpec.isUniqueIdIncluded()) {
args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
-
+ }
return args;
}
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index c8c1de4..f22b604 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -34,6 +34,14 @@
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
+ /**
+ * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the
+ * array.
+ */
+ public static int[] cloneIfNotEmpty(int[] array) {
+ return ((array != null) && (array.length > 0)) ? array.clone() : array;
+ }
+
public static byte[] cloneIfNotEmpty(byte[] array) {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c2a7b2e..e92eaca 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -267,6 +267,7 @@
private final boolean mUserPresenceRequired;
private final byte[] mAttestationChallenge;
private final boolean mDevicePropertiesAttestationIncluded;
+ private final int[] mAttestationIds;
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mInvalidatedByBiometricEnrollment;
@@ -308,6 +309,7 @@
boolean userPresenceRequired,
byte[] attestationChallenge,
boolean devicePropertiesAttestationIncluded,
+ int[] attestationIds,
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
@@ -361,6 +363,7 @@
mUserAuthenticationType = userAuthenticationType;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded;
+ mAttestationIds = attestationIds;
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
@@ -720,6 +723,25 @@
}
/**
+ * @hide
+ * Allows the caller to specify device IDs to be attested to in the certificate for the
+ * generated key pair. These values are the enums specified in
+ * {@link android.security.keystore.AttestationUtils}
+ *
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+ * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+ *
+ * @return integer array representing the requested device IDs to attest.
+ */
+ @SystemApi
+ @Nullable
+ public int[] getAttestationIds() {
+ return Utils.cloneIfNotNull(mAttestationIds);
+ }
+
+ /**
* @hide This is a system-only API
*
* Returns {@code true} if the attestation certificate will contain a unique ID field.
@@ -834,6 +856,7 @@
private boolean mUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mDevicePropertiesAttestationIncluded = false;
+ private int[] mAttestationIds = null;
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
@@ -902,6 +925,7 @@
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mDevicePropertiesAttestationIncluded =
sourceSpec.isDevicePropertiesAttestationIncluded();
+ mAttestationIds = sourceSpec.getAttestationIds();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
@@ -1473,6 +1497,26 @@
}
/**
+ * @hide
+ * Sets which IDs to attest in the attestation certificate for the key. The acceptable
+ * values in this integer array are the enums specified in
+ * {@link android.security.keystore.AttestationUtils}
+ *
+ * @param attestationIds the array of ID types to attest to in the certificate.
+ *
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+ * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+ * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+ */
+ @SystemApi
+ @NonNull
+ public Builder setAttestationIds(@NonNull int[] attestationIds) {
+ mAttestationIds = attestationIds;
+ return this;
+ }
+
+ /**
* @hide Only system apps can use this method.
*
* Sets whether to include a temporary unique ID field in the attestation certificate.
@@ -1638,6 +1682,7 @@
mUserPresenceRequired,
mAttestationChallenge,
mDevicePropertiesAttestationIncluded,
+ mAttestationIds,
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
mInvalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 8163472..1f2f853 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
out.writeBoolean(mSpec.isUserPresenceRequired());
out.writeByteArray(mSpec.getAttestationChallenge());
out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded());
+ out.writeIntArray(mSpec.getAttestationIds());
out.writeBoolean(mSpec.isUniqueIdIncluded());
out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
@@ -160,6 +161,7 @@
final boolean userPresenceRequired = in.readBoolean();
final byte[] attestationChallenge = in.createByteArray();
final boolean devicePropertiesAttestationIncluded = in.readBoolean();
+ final int[] attestationIds = in.createIntArray();
final boolean uniqueIdIncluded = in.readBoolean();
final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
final boolean invalidatedByBiometricEnrollment = in.readBoolean();
@@ -195,6 +197,7 @@
userPresenceRequired,
attestationChallenge,
devicePropertiesAttestationIncluded,
+ attestationIds,
uniqueIdIncluded,
userAuthenticationValidWhileOnBody,
invalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 5722c7b..e58b1cc 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -33,4 +33,8 @@
static byte[] cloneIfNotNull(byte[] value) {
return (value != null) ? value.clone() : null;
}
+
+ static int[] cloneIfNotNull(int[] value) {
+ return (value != null) ? value.clone() : null;
+ }
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 70e30d2..4d27c34 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,16 +18,20 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
+import android.security.keystore.AttestationUtils;
+import android.security.keystore.DeviceIdAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
@@ -38,6 +42,8 @@
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.Log;
import libcore.util.EmptyArray;
@@ -478,7 +484,8 @@
}
throw p;
}
- } catch (UnrecoverableKeyException e) {
+ } catch (UnrecoverableKeyException | IllegalArgumentException
+ | DeviceIdAttestationException e) {
throw new ProviderException(
"Failed to construct key object from newly generated key pair.", e);
} finally {
@@ -496,7 +503,7 @@
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
- throws ProviderException {
+ throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
@@ -526,15 +533,69 @@
Build.MODEL.getBytes(StandardCharsets.UTF_8)
));
}
- } else {
- if (mSpec.isDevicePropertiesAttestationIncluded()) {
- throw new ProviderException("An attestation challenge must be provided when "
- + "requesting device properties attestation.");
+
+ int[] idTypes = mSpec.getAttestationIds();
+ if (idTypes == null) {
+ return;
+ }
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+ || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+ telephonyService =
+ (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException("Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case AttestationUtils.ID_TYPE_SERIAL:
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ case AttestationUtils.ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ }
+ case AttestationUtils.ID_TYPE_MEID: {
+ final String meid = telephonyService.getMeid(0);
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8)
+ ));
+ break;
+ }
+ case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+ params.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
}
}
}
- private Collection<KeyParameter> constructKeyGenerationArguments() {
+ private Collection<KeyParameter> constructKeyGenerationArguments()
+ throws DeviceIdAttestationException, IllegalArgumentException {
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
params.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
package com.android.wm.shell;
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
*/
+@ExternalThread
public interface ShellCommandHandler {
/**
* Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
@@ -38,24 +37,24 @@
public final class ShellCommandHandlerImpl {
private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
- private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
- private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<HideDisplayCutout> mHideDisplayCutout;
+ private final Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+ private final Optional<AppPairsController> mAppPairsOptional;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<AppPairs> mAppPairsOptional;
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
public static ShellCommandHandler create(
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
ShellExecutor mainExecutor) {
return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
private ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -39,9 +38,9 @@
private final DisplayImeController mDisplayImeController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
- private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<AppPairs> mAppPairsOptional;
+ private final Optional<AppPairsController> mAppPairsOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
@@ -51,9 +50,9 @@
public static ShellInit create(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
+ Optional<AppPairsController> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
private ShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
+ Optional<AppPairsController> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
// Register the shell organizer
mShellTaskOrganizer.registerOrganizer();
- mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+ mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
// Bind the splitscreen impl to the drag drop controller
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a570c0a..b22f358 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,7 +44,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
@@ -108,20 +108,20 @@
* compat.
*/
@Nullable
- private final SizeCompatUI mSizeCompatUI;
+ private final SizeCompatUIController mSizeCompatUI;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- SizeCompatUI sizeCompatUI) {
+ SizeCompatUIController sizeCompatUI) {
this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
- Context context, @Nullable SizeCompatUI sizeCompatUI) {
+ Context context, @Nullable SizeCompatUIController sizeCompatUI) {
super(taskOrganizerController, mainExecutor);
// TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
// by a controller, that class should be create while porting
@@ -342,8 +342,8 @@
}
/**
- * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
- * the UI accordingly.
+ * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+ * to update the UI accordingly.
*
* @param taskInfo the new Task info
* @param taskListener listener to handle the Task Surface placement. {@code null} if task is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
+ private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
mShellExecutor = shellExecutor;
}
+ public TaskViewFactory asTaskViewFactory() {
+ return mImpl;
+ }
+
/** Creates an {@link TaskView} */
- @ShellMainThread
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
TaskView taskView = new TaskView(context, mTaskOrganizer);
executor.execute(() -> {
@@ -46,10 +50,6 @@
});
}
- public TaskViewFactory getTaskViewFactory() {
- return new TaskViewFactoryImpl();
- }
-
private class TaskViewFactoryImpl implements TaskViewFactory {
@ExternalThread
public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
/**
* The singleton wrapper to communicate between WindowManagerService and WMShell features
* (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
*/
public class WindowManagerShellWrapper {
private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
/** Unpairs any app-pair containing this task id. */
void unpair(int taskId);
- /** Dumps current status of app pairs. */
- void dump(@NonNull PrintWriter pw, String prefix);
- /** Called when the shell organizer has been registered. */
- void onOrganizerRegistered();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
private final DisplayController mDisplayController;
- /**
- * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
- */
- @Nullable
- public static AppPairs create(ShellTaskOrganizer organizer,
- SyncTransactionQueue syncQueue, DisplayController displayController,
- ShellExecutor mainExecutor) {
- return new AppPairsController(organizer, syncQueue, displayController,
- mainExecutor).mImpl;
- }
-
- AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+ public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
DisplayController displayController, ShellExecutor mainExecutor) {
mTaskOrganizer = organizer;
mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
mMainExecutor = mainExecutor;
}
- void onOrganizerRegistered() {
+ public AppPairs asAppPairs() {
+ return mImpl;
+ }
+
+ public void onOrganizerRegistered() {
if (mPairsPool == null) {
setPairsPool(new AppPairsPool(this));
}
}
@VisibleForTesting
- void setPairsPool(AppPairsPool pool) {
+ public void setPairsPool(AppPairsPool pool) {
mPairsPool = pool;
}
- boolean pair(int taskId1, int taskId2) {
+ public boolean pair(int taskId1, int taskId2) {
final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
return pair(task1, task2);
}
- boolean pair(ActivityManager.RunningTaskInfo task1,
+ public boolean pair(ActivityManager.RunningTaskInfo task1,
ActivityManager.RunningTaskInfo task2) {
return pairInner(task1, task2) != null;
}
@VisibleForTesting
- AppPair pairInner(
+ public AppPair pairInner(
@NonNull ActivityManager.RunningTaskInfo task1,
@NonNull ActivityManager.RunningTaskInfo task2) {
final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
return pair;
}
- void unpair(int taskId) {
+ public void unpair(int taskId) {
unpair(taskId, true /* releaseToPool */);
}
- void unpair(int taskId, boolean releaseToPool) {
+ public void unpair(int taskId, boolean releaseToPool) {
AppPair pair = mActiveAppPairs.get(taskId);
if (pair == null) {
for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
}
}
- ShellTaskOrganizer getTaskOrganizer() {
+ public ShellTaskOrganizer getTaskOrganizer() {
return mTaskOrganizer;
}
- SyncTransactionQueue getSyncTransactionQueue() {
+ public SyncTransactionQueue getSyncTransactionQueue() {
return mSyncQueue;
}
- DisplayController getDisplayController() {
+ public DisplayController getDisplayController() {
return mDisplayController;
}
- private void dump(@NonNull PrintWriter pw, String prefix) {
+ public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
@@ -202,21 +195,5 @@
AppPairsController.this.unpair(taskId);
});
}
-
- @Override
- public void onOrganizerRegistered() {
- mMainExecutor.execute(() -> {
- AppPairsController.this.onOrganizerRegistered();
- });
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- try {
- mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump AppPairsController in 2s");
- }
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d73fc6d..047df5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -86,6 +86,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -192,9 +193,9 @@
private boolean mIsStatusBarShade = true;
/**
- * Injected constructor.
+ * Creates an instance of the BubbleController.
*/
- public static Bubbles create(Context context,
+ public static BubbleController create(Context context,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@Nullable IStatusBarService statusBarService,
@@ -211,14 +212,14 @@
return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
- logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+ logger, organizer, positioner, mainExecutor, mainHandler);
}
/**
* Testing constructor.
*/
@VisibleForTesting
- public BubbleController(Context context,
+ protected BubbleController(Context context,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +323,7 @@
}
@VisibleForTesting
- public Bubbles getImpl() {
+ public Bubbles asBubbles() {
return mImpl;
}
@@ -586,11 +587,15 @@
// There were no bubbles saved for this used.
return;
}
- for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) {
- if (canLaunchInActivityView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
- }
- }
+ mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+ mMainExecutor.execute(() -> {
+ for (BubbleEntry e : entries) {
+ if (canLaunchInActivityView(mContext, e)) {
+ updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+ }
+ }
+ });
+ });
// Finally, remove the entries for this user now that bubbles are restored.
mSavedBubbleKeysPerUser.remove(mCurrentUserId);
}
@@ -856,21 +861,24 @@
}
}
- private void onRankingUpdated(RankingMap rankingMap) {
+ private void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
if (mTmpRanking == null) {
mTmpRanking = new NotificationListenerService.Ranking();
}
String[] orderedKeys = rankingMap.getOrderedKeys();
for (int i = 0; i < orderedKeys.length; i++) {
String key = orderedKeys[i];
- BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key);
+ Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
+ BubbleEntry entry = entryData.first;
+ boolean shouldBubbleUp = entryData.second;
rankingMap.getRanking(key, mTmpRanking);
boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
if (isActiveBubble && !mTmpRanking.canBubble()) {
// If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
// This means that the app or channel's ability to bubble has been revoked.
mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
- } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) {
+ } else if (isActiveBubble && !shouldBubbleUp) {
// If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
// This happens when DND is enabled and configured to hide bubbles. Dismissing with
// the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
@@ -919,17 +927,20 @@
private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
Objects.requireNonNull(b);
b.setIsBubble(isBubble);
- final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey());
- if (entry != null) {
- // Updating the entry to be a bubble will trigger our normal update flow
- setIsBubble(entry, isBubble, b.shouldAutoExpand());
- } else if (isBubble) {
- // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
- // stack ourselves
- Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
- inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
- !bubble.shouldAutoExpand() /* showInShade */);
- }
+ mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
+ mMainExecutor.execute(() -> {
+ if (entry != null) {
+ // Updating the entry to be a bubble will trigger our normal update flow
+ setIsBubble(entry, isBubble, b.shouldAutoExpand());
+ } else if (isBubble) {
+ // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
+ // stack ourselves
+ Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
+ inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
+ !bubble.shouldAutoExpand() /* showInShade */);
+ }
+ });
+ });
}
@SuppressWarnings("FieldCanBeLocal")
@@ -992,14 +1003,17 @@
}
}
- final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
- if (entry != null) {
- final String groupKey = entry.getStatusBarNotification().getGroupKey();
- if (getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
- }
- }
+ mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> {
+ mMainExecutor.execute(() -> {
+ if (entry != null) {
+ final String groupKey = entry.getStatusBarNotification().getGroupKey();
+ if (getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to potentially remove the summary
+ mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
+ }
+ }
+ });
+ });
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
@@ -1121,23 +1135,6 @@
mStackView.updateContentDescription();
}
- /**
- * The task id of the expanded view, if the stack is expanded and not occluded by the
- * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
- */
- private int getExpandedTaskId() {
- if (mStackView == null) {
- return INVALID_TASK_ID;
- }
- final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
- if (expandedViewProvider != null && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mSysuiProxy.isNotificationShadeExpand()) {
- return expandedViewProvider.getTaskId();
- }
- return INVALID_TASK_ID;
- }
-
@VisibleForTesting
public BubbleStackView getStackView() {
return mStackView;
@@ -1343,9 +1340,10 @@
}
@Override
- public void onRankingUpdated(RankingMap rankingMap) {
+ public void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
mMainExecutor.execute(() -> {
- BubbleController.this.onRankingUpdated(rankingMap);
+ BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2100428..2f31acd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -163,6 +163,7 @@
// Apply flags to make behaviour match documentLaunchMode=always.
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true);
if (mBubble != null) {
mBubble.setIntentActive();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 6a1026b..8e061e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -26,6 +26,7 @@
import android.os.Looper;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.IntDef;
@@ -37,6 +38,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -182,8 +184,11 @@
* permissions on the notification channel or the global setting.
*
* @param rankingMap the updated ranking map from NotificationListenerService
+ * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
+ * bubble up
*/
- void onRankingUpdated(RankingMap rankingMap);
+ void onRankingUpdated(RankingMap rankingMap,
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);
/**
* Called when the status bar has become visible or invisible (either permanently or
@@ -243,14 +248,10 @@
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
- @Nullable
- BubbleEntry getPendingOrActiveEntry(String key);
+ void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
- List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys);
-
- boolean isNotificationShadeExpand();
-
- boolean shouldBubbleUp(String key);
+ void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ Consumer<List<BubbleEntry>> callback);
void setNotificationInterruption(String key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
public interface HideDisplayCutout {
/**
* Notifies {@link Configuration} changed.
- * @param newConfig
*/
void onConfigurationChanged(Configuration newConfig);
-
- /**
- * Dumps hide display cutout status.
- */
- void dump(@NonNull PrintWriter pw);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
@VisibleForTesting
boolean mEnabled;
- HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
- ShellExecutor mainExecutor) {
- mContext = context;
- mOrganizer = organizer;
- mMainExecutor = mainExecutor;
- updateStatus();
- }
-
/**
* Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
* supported.
*/
@Nullable
- public static HideDisplayCutout create(
+ public static HideDisplayCutoutController create(
Context context, DisplayController displayController, ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+ return new HideDisplayCutoutController(context, organizer, mainExecutor);
+ }
+
+ HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+ ShellExecutor mainExecutor) {
+ mContext = context;
+ mOrganizer = organizer;
+ mMainExecutor = mainExecutor;
+ updateStatus();
+ }
+
+ public HideDisplayCutout asHideDisplayCutout() {
+ return mImpl;
}
@VisibleForTesting
@@ -94,7 +98,7 @@
updateStatus();
}
- private void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
final String prefix = " ";
pw.print(TAG);
pw.println(" states: ");
@@ -111,14 +115,5 @@
HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
});
}
-
- @Override
- public void dump(@NonNull PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
- }
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
private volatile boolean mAdjustedForIme = false;
private boolean mHomeStackResizable = false;
- /**
- * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
- */
- @Nullable
- public static LegacySplitScreen create(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController imeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
- return new LegacySplitScreenController(context, displayController, systemWindows,
- imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
- transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
- }
-
public LegacySplitScreenController(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
}
});
}
+
+ public LegacySplitScreen asLegacySplitScreen() {
+ return mImpl;
+ }
- void onSplitScreenSupported() {
+ public void onSplitScreenSupported() {
// Set starting tile bounds based on middle target
final WindowContainerTransaction tct = new WindowContainerTransaction();
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
mTaskOrganizer.applyTransaction(tct);
}
- private void onKeyguardVisibilityChanged(boolean showing) {
+ public void onKeyguardVisibilityChanged(boolean showing) {
if (!isSplitActive() || mView == null) {
return;
}
@@ -293,19 +282,19 @@
}
}
- boolean isMinimized() {
+ public boolean isMinimized() {
return mMinimized;
}
- boolean isHomeStackResizable() {
+ public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
- DividerView getDividerView() {
+ public DividerView getDividerView() {
return mView;
}
- boolean isDividerVisible() {
+ public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
@@ -314,13 +303,13 @@
* isDividerVisible because the divider is only visible once *everything* is in split mode
* while this only cares if some things are (eg. while entering/exiting as well).
*/
- private boolean isSplitActive() {
+ public boolean isSplitActive() {
return mSplits.mPrimary != null && mSplits.mSecondary != null
&& (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
|| mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
}
- private void addDivider(Configuration configuration) {
+ public void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
- private void removeDivider() {
+ public void removeDivider() {
if (mView != null) {
mView.onDividerRemoved();
}
mWindowManager.remove();
}
- private void update(Configuration configuration) {
+ public void update(Configuration configuration) {
final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
removeDivider();
@@ -358,11 +347,11 @@
mView.setHidden(isDividerHidden);
}
- void onTaskVanished() {
+ public void onTaskVanished() {
removeDivider();
}
- private void updateVisibility(final boolean visible) {
+ public void updateVisibility(final boolean visible) {
if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
mVisible = visible;
@@ -390,7 +379,7 @@
}
}
- private void setMinimized(final boolean minimized) {
+ public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
});
}
- private void setHomeMinimized(final boolean minimized) {
+ public void setHomeMinimized(final boolean minimized) {
if (DEBUG) {
Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
+ mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
}
}
- void setAdjustedForIme(boolean adjustedForIme) {
+ public void setAdjustedForIme(boolean adjustedForIme) {
if (mAdjustedForIme == adjustedForIme) {
return;
}
@@ -449,30 +438,30 @@
updateTouchable();
}
- private void updateTouchable() {
+ public void updateTouchable() {
mWindowManager.setTouchable(!mAdjustedForIme);
}
- private void onUndockingTask() {
+ public void onUndockingTask() {
if (mView != null) {
mView.onUndockingTask();
}
}
- private void onAppTransitionFinished() {
+ public void onAppTransitionFinished() {
if (mView == null) {
return;
}
mForcedResizableController.onAppTransitionFinished();
}
- private void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw) {
pw.print(" mVisible="); pw.println(mVisible);
pw.print(" mMinimized="); pw.println(mMinimized);
pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
}
- long getAnimDuration() {
+ public long getAnimDuration() {
float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE,
mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
return (long) (transitionDuration * transitionScale);
}
- void registerInSplitScreenListener(Consumer<Boolean> listener) {
+ public void registerInSplitScreenListener(Consumer<Boolean> listener) {
listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
}
- void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+ public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
synchronized (mDockedStackExistsListeners) {
for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
}
}
- private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+ public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.add(new WeakReference<>(listener));
}
}
- private boolean splitPrimaryTask() {
+ public boolean splitPrimaryTask() {
try {
if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
|| isSplitActive()) {
@@ -538,12 +527,12 @@
topRunningTask.taskId, true /* onTop */);
}
- private void dismissSplitToPrimaryTask() {
+ public void dismissSplitToPrimaryTask() {
startDismissSplit(true /* toPrimaryTask */);
}
/** Notifies the bounds of split screen changed. */
- void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+ public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.removeIf(wf -> {
BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
}
}
- void startEnterSplit() {
+ public void startEnterSplit() {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
// Set resizable directly here because applyEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
}
- void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+ public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
// Set resizable directly here because buildEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
}
- void finishEnterSplitTransition(boolean minimized) {
+ public void finishEnterSplitTransition(boolean minimized) {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
if (minimized) {
@@ -575,11 +564,11 @@
}
}
- void startDismissSplit(boolean toPrimaryTask) {
+ public void startDismissSplit(boolean toPrimaryTask) {
startDismissSplit(toPrimaryTask, false /* snapped */);
}
- void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+ public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mSplits.getSplitTransitions().dismissSplit(
mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
}
}
- void onDismissSplit() {
+ public void onDismissSplit() {
updateVisibility(false /* visible */);
mMinimized = false;
// Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
mImePositionProcessor.reset();
}
- void ensureMinimizedSplit() {
+ public void ensureMinimizedSplit() {
setHomeMinimized(true /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
}
}
- void ensureNormalSplit() {
+ public void ensureNormalSplit() {
setHomeMinimized(false /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
}
}
- LegacySplitDisplayLayout getSplitLayout() {
+ public LegacySplitDisplayLayout getSplitLayout() {
return mSplitLayout;
}
- WindowManagerProxy getWmProxy() {
+ public WindowManagerProxy getWmProxy() {
return mWindowManagerProxy;
}
- WindowContainerToken getSecondaryRoot() {
+ public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
return null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 94c6f01..c8f8987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -39,7 +39,6 @@
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -116,7 +115,7 @@
void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
WindowContainerTransaction t = new WindowContainerTransaction();
splitLayout.resizeSplits(position, t);
- new WindowOrganizer().applyTransaction(t);
+ applySyncTransaction(t);
}
boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
* 3 button navigation mode only
*/
void registerGestureCallback(OneHandedGestureEventCallback callback);
-
- /**
- * Dump one handed status.
- */
- void dump(@NonNull PrintWriter pw);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
/**
- * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+ * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
@Nullable
- public static OneHanded create(
+ public static OneHandedController create(
Context context, DisplayController displayController,
TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
return new OneHandedController(context, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
- taskStackListener, mainExecutor, mainHandler).mImpl;
+ taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
@@ -228,6 +228,10 @@
mAccessibilityStateChangeListener);
}
+ public OneHanded asOneHanded() {
+ return mImpl;
+ }
+
/**
* Set one handed enabled or disabled when user update settings
*/
@@ -468,7 +472,7 @@
}
}
- private void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG + "states: ");
pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
OneHandedController.this.registerGestureCallback(callback);
});
}
-
- @Override
- public void dump(@NonNull PrintWriter pw) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.dump(pw);
- });
- }
}
}
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 ad6f435..3064af6 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
@@ -67,6 +67,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.transition.Transitions;
@@ -131,7 +132,7 @@
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<LegacySplitScreenController> mSplitScreenOptional;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -207,7 +208,7 @@
@NonNull PipAnimationController pipAnimationController,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
@NonNull PipTransitionController pipTransitionController,
- Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<LegacySplitScreenController> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -1047,7 +1048,8 @@
}
/**
- * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+ * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+ * screen.
*
* @param destinationBoundsOut contain the updated destination bounds if applicable
* @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1059,7 @@
return false;
}
- LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+ LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
if (!legacySplitScreen.isDividerVisible()) {
// fail early if system is not in split screen mode
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 53571ff..1ef9ffa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -558,8 +558,8 @@
|| mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
}
- mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
- mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +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.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
- /**
- * Called when the Task info changed. Creates and updates the restart button if there is an
- * activity in size compat, or removes the restart button if there is no size compat activity.
- *
- * @param displayId display the task and activity are in.
- * @param taskId task the activity is in.
- * @param taskBounds task bounds to place the restart button in.
- * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
- * top activity in this Task is not in size compat.
- * @param taskListener listener to handle the Task Surface placement.
- */
- void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
- @Nullable IBinder sizeCompatActivity,
- @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 286c3b6..48ee86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -48,7 +48,6 @@
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@VisibleForTesting
- final SizeCompatUI mImpl = new SizeCompatUIImpl();
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final DisplayController mDisplayController;
@@ -57,17 +56,8 @@
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
- /** Creates the {@link SizeCompatUIController}. */
- public static SizeCompatUI create(Context context,
- DisplayController displayController,
- DisplayImeController imeController,
- ShellExecutor mainExecutor) {
- return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
- .mImpl;
- }
-
@VisibleForTesting
- SizeCompatUIController(Context context,
+ public SizeCompatUIController(Context context,
DisplayController displayController,
DisplayImeController imeController,
ShellExecutor mainExecutor) {
@@ -79,7 +69,7 @@
mImeController.addPositionProcessor(this);
}
- private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+ public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
@Nullable IBinder sizeCompatActivity,
@Nullable ShellTaskOrganizer.TaskListener taskListener) {
// TODO Draw button on Task surface
@@ -177,15 +167,4 @@
}
return context;
}
-
- private class SizeCompatUIImpl implements SizeCompatUI {
- @Override
- public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
- @Nullable IBinder sizeCompatActivity,
- @Nullable ShellTaskOrganizer.TaskListener taskListener) {
- mMainExecutor.execute(() ->
- SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
- taskBounds, sizeCompatActivity, taskListener));
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index a6f44ef..b7fd3cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -43,6 +43,7 @@
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
@@ -498,7 +499,7 @@
}
}
- @ExternalThread
+ @BinderThread
static class Window extends BaseIWindow {
private TaskSnapshotWindow mOuter;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 111362a..ecc066b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -24,7 +24,6 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -113,7 +112,7 @@
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
- uiDevice.closePipWindow()
+ closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index a14b46e..d56ed02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -50,8 +48,7 @@
@JvmStatic
fun getParams(): List<Array<Any>> {
val testApp = FixedAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
@@ -97,7 +94,7 @@
}
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 99a40da..ff31ba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -50,9 +48,8 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(
- eachRun = true, stringExtras = emptyMap())
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true,
+ stringExtras = emptyMap()) { configuration ->
transitions {
pipApp.clickEnterPipButton()
pipApp.expandPipWindow(wmHelper)
@@ -92,7 +89,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 7576e24..f054e64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
@@ -48,8 +46,7 @@
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val imeApp = ImeAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = false)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = false) { configuration ->
setup {
test {
imeApp.launchViaIntent(wmHelper)
@@ -90,7 +87,7 @@
}
return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
- baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0),
repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index adab5e8..ade65ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
@@ -55,8 +53,7 @@
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val fixedApp = FixedAppHelper(instrumentation)
- val baseConfig = getTransitionLaunch(eachRun = false)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = false) { configuration ->
setup {
test {
fixedApp.launchViaIntent(wmHelper)
@@ -112,8 +109,7 @@
}
return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
- baseConfig, testSpec,
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+ testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 4b826ff..f2d5899 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
@@ -110,7 +107,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 62e8221..1b44377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,13 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.os.Bundle
import android.view.Surface
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<Array<Any>> {
- val baseConfig = getTransitionLaunch(eachRun = true)
- val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+ val testSpec = getTransition(eachRun = true) { configuration ->
setup {
eachRun {
this.setRotation(configuration.startRotation)
@@ -111,7 +108,7 @@
}
}
- return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+ return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
index eb7bae1..b1e404e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -22,8 +22,6 @@
import android.view.Surface
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
@@ -83,10 +81,6 @@
}
test {
removeAllTasksButHome()
-
- if (device.hasPipWindow()) {
- device.closePipWindow()
- }
pipApp.exit()
}
}
@@ -98,11 +92,13 @@
*
* @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
* @param stringExtras Arguments to pass to the PIP launch intent
+ * @param extraSpec Addicional segment of flicker specification
*/
@JvmOverloads
- fun getTransitionLaunch(
+ open fun getTransition(
eachRun: Boolean,
- stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+ stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+ extraSpec: FlickerBuilder.(Bundle) -> Unit = {}
): FlickerBuilder.(Bundle) -> Unit {
return { configuration ->
setupAndTeardown(this, configuration)
@@ -135,6 +131,8 @@
removeAllTasksButHome()
}
}
+
+ extraSpec(this, configuration)
}
}
}
\ 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 80ea9b9..176b33d 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
@@ -51,7 +51,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import org.junit.Before;
import org.junit.Test;
@@ -76,7 +76,7 @@
@Mock
private Context mContext;
@Mock
- private SizeCompatUI mSizeCompatUI;
+ private SizeCompatUIController mSizeCompatUI;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import org.junit.Before;
@@ -70,7 +71,7 @@
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
- @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+ @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 98f01ff..0eb64e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -86,7 +86,7 @@
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
// Verify that the restart button is added with non-null size compat activity.
- mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
mMockActivityToken, mMockTaskListener);
mShellMainExecutor.flushAll();
@@ -94,7 +94,7 @@
verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
// Verify that the restart button is removed with null size compat activity.
- mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
mShellMainExecutor.flushAll();
verify(mMockButton).remove();
@@ -104,7 +104,7 @@
public void testChangeButtonVisibilityOnImeShowHide() {
final int taskId = 12;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
- mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
mMockActivityToken, mMockTaskListener);
mShellMainExecutor.flushAll();
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index ca5981c0..9c743ce 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -83,8 +83,16 @@
return {};
}
+ std::unique_ptr<AssetsProvider> overlay_assets;
const std::string overlay_path(loaded_idmap->OverlayApkPath());
- auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
+ if (IsFabricatedOverlay(overlay_path)) {
+ // Fabricated overlays do not contain resource definitions. All of the overlay resource values
+ // are defined inline in the idmap.
+ overlay_assets = EmptyAssetsProvider::Create();
+ } else {
+ // The overlay should be an APK.
+ overlay_assets = ZipAssetsProvider::Create(overlay_path);
+ }
if (overlay_assets == nullptr) {
return {};
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 03ab62f..36bde5c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -102,9 +102,8 @@
memset(&configuration_, 0, sizeof(configuration_));
}
-bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
- bool invalidate_caches) {
- apk_assets_ = apk_assets;
+bool AssetManager2::SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches) {
+ apk_assets_ = std::move(apk_assets);
BuildDynamicRefTable();
RebuildFilterList();
if (invalidate_caches) {
@@ -137,6 +136,36 @@
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
for (const ApkAssets* apk_assets : sorted_apk_assets) {
+ std::shared_ptr<OverlayDynamicRefTable> overlay_ref_table;
+ if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
+ // The target package must precede the overlay package in the apk assets paths in order
+ // to take effect.
+ auto iter = apk_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+ if (iter == apk_assets_package_ids.end()) {
+ LOG(INFO) << "failed to find target package for overlay "
+ << loaded_idmap->OverlayApkPath();
+ } else {
+ uint8_t target_package_id = iter->second;
+
+ // Create a special dynamic reference table for the overlay to rewrite references to
+ // overlay resources as references to the target resources they overlay.
+ overlay_ref_table = std::make_shared<OverlayDynamicRefTable>(
+ loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
+
+ // Add the overlay resource map to the target package's set of overlays.
+ const uint8_t target_idx = package_ids_[target_package_id];
+ CHECK(target_idx != 0xff) << "overlay target '" << loaded_idmap->TargetApkPath()
+ << "'added to apk_assets_package_ids but does not have an"
+ << " assigned package group";
+
+ PackageGroup& target_package_group = package_groups_[target_idx];
+ target_package_group.overlays_.push_back(
+ ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
+ overlay_ref_table.get()),
+ apk_assets_cookies[apk_assets]});
+ }
+ }
+
const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
@@ -147,50 +176,25 @@
package_id = package->GetPackageId();
}
- // Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
+ // Add the mapping for package ID to index if not present.
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
- package_groups_.push_back({});
+ PackageGroup& new_group = package_groups_.emplace_back();
- if (apk_assets->IsOverlay()) {
- // The target package must precede the overlay package in the apk assets paths in order
- // to take effect.
- const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
- auto target_package_iter = apk_assets_package_ids.find(
- std::string(loaded_idmap->TargetApkPath()));
- if (target_package_iter == apk_assets_package_ids.end()) {
- LOG(INFO) << "failed to find target package for overlay "
- << loaded_idmap->OverlayApkPath();
- } else {
- const uint8_t target_package_id = target_package_iter->second;
- const uint8_t target_idx = package_ids_[target_package_id];
- CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
- << " have an assigned package group";
-
- PackageGroup& target_package_group = package_groups_[target_idx];
-
- // Create a special dynamic reference table for the overlay to rewrite references to
- // overlay resources as references to the target resources they overlay.
- auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
- loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
- package_groups_.back().dynamic_ref_table = overlay_table;
-
- // Add the overlay resource map to the target package's set of overlays.
- target_package_group.overlays_.push_back(
- ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
- overlay_table.get()),
- apk_assets_cookies[apk_assets]});
- }
+ if (overlay_ref_table != nullptr) {
+ // If this package is from an overlay, use a dynamic reference table that can rewrite
+ // overlay resource ids to their corresponding target resource ids.
+ new_group.dynamic_ref_table = overlay_ref_table;
}
- DynamicRefTable* ref_table = package_groups_.back().dynamic_ref_table.get();
+ DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
ref_table->mAssignedPackageId = package_id;
ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
- PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
+ PackageGroup* package_group = &package_groups_[idx];
package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
@@ -578,7 +582,7 @@
const PackageGroup& package_group = package_groups_[package_idx];
auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
+ stop_at_first_match, ignore_configuration);
if (UNLIKELY(!result.has_value())) {
return base::unexpected(result.error());
}
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 23cacf8..f3c48f7 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -84,7 +84,7 @@
return value_;
}
-ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
+ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
time_t last_mod_time)
: zip_handle_(handle, ::CloseArchive),
name_(std::forward<PathOrDebugName>(path)),
@@ -93,7 +93,7 @@
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
ZipArchiveHandle handle;
if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
- LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+ LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
@@ -253,6 +253,14 @@
return result == -1;
}
+std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
+ ::ZipEntry entry;
+ if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
+ return {};
+ }
+ return entry.crc32;
+}
+
const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index f216f55..efd1f6a 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -54,12 +54,6 @@
};
struct Idmap_data_header {
- uint8_t target_package_id;
- uint8_t overlay_package_id;
-
- // Padding to ensure 4 byte alignment for target_entry_count
- uint16_t p0;
-
uint32_t target_entry_count;
uint32_t target_inline_entry_count;
uint32_t overlay_entry_count;
@@ -158,19 +152,19 @@
return {};
}
- // The resource ids encoded within the idmap are build-time resource ids.
- target_res_id = (0x00FFFFFFU & target_res_id)
- | (((uint32_t) data_header_->target_package_id) << 24U);
+ // The resource ids encoded within the idmap are build-time resource ids so do not consider the
+ // package id when determining if the resource in the target package is overlaid.
+ target_res_id &= 0x00FFFFFFU;
// Check if the target resource is mapped to an overlay resource.
auto first_entry = entries_;
auto end_entry = entries_ + dtohl(data_header_->target_entry_count);
auto entry = std::lower_bound(first_entry, end_entry, target_res_id,
- [](const Idmap_target_entry &e, const uint32_t target_id) {
- return dtohl(e.target_id) < target_id;
+ [](const Idmap_target_entry& e, const uint32_t target_id) {
+ return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
});
- if (entry != end_entry && dtohl(entry->target_id) == target_res_id) {
+ if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) {
uint32_t overlay_resource_id = dtohl(entry->overlay_id);
// Lookup the resource without rewriting the overlay resource id back to the target resource id
// being looked up.
@@ -182,12 +176,13 @@
auto first_inline_entry = inline_entries_;
auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count);
auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id,
- [](const Idmap_target_entry_inline &e,
+ [](const Idmap_target_entry_inline& e,
const uint32_t target_id) {
- return dtohl(e.target_id) < target_id;
+ return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
});
- if (inline_entry != end_inline_entry && dtohl(inline_entry->target_id) == target_res_id) {
+ if (inline_entry != end_inline_entry &&
+ (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
return Result(inline_entry->value);
}
return {};
@@ -235,7 +230,7 @@
}
return std::string_view(data, *len);
}
-}
+} // namespace
LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
const Idmap_header* header,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2233827..30500ab 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -25,6 +25,7 @@
#include <string.h>
#include <algorithm>
+#include <fstream>
#include <limits>
#include <map>
#include <memory>
@@ -44,6 +45,7 @@
#ifdef __ANDROID__
#include <binder/TextOutput.h>
+
#endif
#ifndef INT32_MAX
@@ -233,6 +235,15 @@
fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
}
+bool IsFabricatedOverlay(const std::string& path) {
+ std::ifstream fin(path);
+ uint32_t magic;
+ if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
+ return magic == kFabricatedOverlayMagic;
+ }
+ return false;
+}
+
static bool assertIdmapHeader(const void* idmap, size_t size) {
if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
ALOGE("idmap: header is not word aligned");
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 6fbd6aa..2255973f 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -101,7 +101,7 @@
// Only pass invalidate_caches=false when it is known that the structure
// change in ApkAssets is due to a safe addition of resources with completely
// new resource IDs.
- bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
+ bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
inline const std::vector<const ApkAssets*> GetApkAssets() const {
return apk_assets_;
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 7b06947..6f16ff4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -88,6 +88,8 @@
WARN_UNUSED const std::string& GetDebugName() const override;
WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
+
~ZipAssetsProvider() override = default;
protected:
std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 0ded793..6804472 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -168,15 +168,14 @@
}
// Returns a mapping from target resource ids to overlay values.
- const IdmapResMap GetTargetResourcesMap(
- uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const {
+ const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
+ const OverlayDynamicRefTable* overlay_ref_table) const {
return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
target_assigned_package_id, overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
- const OverlayDynamicRefTable GetOverlayDynamicRefTable(
- uint8_t target_assigned_package_id) const {
+ const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index bfd564c..168a863 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -43,8 +43,19 @@
namespace android {
-constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u;
+constexpr const uint32_t kIdmapMagic = 0x504D4449u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
+
+// This must never change.
+constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
+
+// The version should only be changed when a backwards-incompatible change must be made to the
+// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
+// to prevent losing fabricated overlay data.
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+
+// Returns whether or not the path represents a fabricated overlay.
+bool IsFabricatedOverlay(const std::string& path);
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 723413c..88eadcc 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 1dc5cd9..2e4d7f62 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -239,14 +239,12 @@
static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
ScopedUtfChars strSksl(env, sksl);
- auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
- sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
- if (effect.get() == nullptr) {
- const auto& err = std::get<1>(result);
- doThrowIAE(env, err.c_str());
+ auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()), SkRuntimeEffect::Options{});
+ if (result.effect.get() == nullptr) {
+ doThrowIAE(env, result.errorText.c_str());
return 0;
}
- return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect)));
+ return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect)));
}
static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..1842356 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,41 @@
env->SetStaticObjectField(cls, fid, typeface);
}
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+ return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+ std::shared_ptr<minikin::FontFamily> family =
+ toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+ return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
- { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
- { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
- (void*)Typeface_createFromTypefaceWithExactStyle },
- { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
- (void*)Typeface_createFromTypefaceWithVariation },
- { "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias },
- { "nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc },
- { "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
- { "nativeGetWeight", "(J)I", (void*)Typeface_getWeight },
- { "nativeCreateFromArray", "([JJII)J",
- (void*)Typeface_createFromArray },
- { "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
- { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes },
- { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
- (void*)Typeface_registerGenericFamily },
- { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
- { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
- { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
- (void*)Typeface_forceSetStaticFinalField },
+ {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+ {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+ (void*)Typeface_createFromTypefaceWithExactStyle},
+ {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+ (void*)Typeface_createFromTypefaceWithVariation},
+ {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+ {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+ {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+ {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+ {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+ {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+ {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+ {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+ (void*)Typeface_registerGenericFamily},
+ {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+ {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+ {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+ (void*)Typeface_forceSetStaticFinalField},
+ {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+ {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
};
int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
name: "libincident_test",
test_config: "AndroidTest.xml",
defaults: ["libincidentpriv_defaults"],
- test_suites: ["device-tests", "mts"],
+ test_suites: ["device-tests", "mts-statsd"],
compile_multilib: "both",
multilib: {
lib64: {
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index cf2f0f0..a7ed091 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -10,7 +10,7 @@
"include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
},
{
- "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
+ "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
}
]
}
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index 85b4ba5..3c48f8f 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -19,6 +19,7 @@
import android.media.MediaDrm;
import java.util.Arrays;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -94,9 +95,9 @@
* @param data The initialization data.
*/
public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
- this.uuid = uuid;
- this.mimeType = mimeType;
- this.data = data;
+ this.uuid = Objects.requireNonNull(uuid);
+ this.mimeType = Objects.requireNonNull(mimeType);
+ this.data = Objects.requireNonNull(data);
}
@Override
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 02fa94c..5a7ff7f 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -33,7 +33,8 @@
float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
oneway void stop(IBinder token);
boolean isPlaying(IBinder token);
- oneway void setPlaybackProperties(IBinder token, float volume, boolean looping);
+ oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
+ boolean hapticGeneratorEnabled);
/** Used for Notification sound playback. */
oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 1a49b85..67f1660 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
-import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1154,6 +1153,22 @@
public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
/**
+ * An optional key describing the opto-electronic transfer function
+ * requested for the output video content.
+ *
+ * The associated value is an integer: 0 if unspecified, or one of the
+ * COLOR_TRANSFER_ values. When unspecified the component will not touch the
+ * video content; otherwise the component will tone-map the raw video frame
+ * to match the requested transfer function.
+ *
+ * After configure, component's input format will contain this key to note
+ * whether the request is supported or not. If the value in the input format
+ * is the same as the requested value, the request is supported. The value
+ * is set to 0 if unsupported.
+ */
+ public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
+
+ /**
* A key describing a unique ID for the content of a media track.
*
* <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index bd783ce..79d505e 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -24,6 +24,7 @@
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
+import android.media.audiofx.HapticGenerator;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -77,6 +78,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private MediaPlayer mLocalPlayer;
private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+ private HapticGenerator mHapticGenerator;
@UnsupportedAppUsage
private Uri mUri;
@@ -89,6 +91,7 @@
// playback properties, use synchronized with mPlaybackSettingsLock
private boolean mIsLooping = false;
private float mVolume = 1.0f;
+ private boolean mHapticGeneratorEnabled = false;
private final Object mPlaybackSettingsLock = new Object();
/** {@hide} */
@@ -197,15 +200,50 @@
}
/**
+ * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
+ * only be enabled on devices that support the effect.
+ *
+ * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
+ * @see android.media.audiofx.HapticGenerator#isAvailable()
+ */
+ public boolean setHapticGeneratorEnabled(boolean enabled) {
+ if (!HapticGenerator.isAvailable()) {
+ return false;
+ }
+ synchronized (mPlaybackSettingsLock) {
+ mHapticGeneratorEnabled = enabled;
+ applyPlaybackProperties_sync();
+ }
+ return true;
+ }
+
+ /**
+ * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
+ * @return true if the HapticGenerator is enabled.
+ */
+ public boolean isHapticGeneratorEnabled() {
+ synchronized (mPlaybackSettingsLock) {
+ return mHapticGeneratorEnabled;
+ }
+ }
+
+ /**
* Must be called synchronized on mPlaybackSettingsLock
*/
private void applyPlaybackProperties_sync() {
if (mLocalPlayer != null) {
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
+ if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+ mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+ }
+ if (mHapticGenerator != null) {
+ mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+ }
} else if (mAllowRemote && (mRemotePlayer != null)) {
try {
- mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+ mRemotePlayer.setPlaybackProperties(
+ mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
} catch (RemoteException e) {
Log.w(TAG, "Problem setting playback properties: ", e);
}
@@ -413,6 +451,10 @@
private void destroyLocalPlayer() {
if (mLocalPlayer != null) {
+ if (mHapticGenerator != null) {
+ mHapticGenerator.release();
+ mHapticGenerator = null;
+ }
mLocalPlayer.setOnCompletionListener(null);
mLocalPlayer.reset();
mLocalPlayer.release();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
* Pauses TV program recording in the current recording session.
*
* @param params A set of extra parameters which might be handled with this event.
+ * {@link TvRecordingClient#pauseRecording(Bundle)}.
*/
void pauseRecording(@NonNull Bundle params) {
if (mToken == null) {
@@ -2543,6 +2544,7 @@
* Resumes TV program recording in the current recording session.
*
* @param params A set of extra parameters which might be handled with this event.
+ * {@link TvRecordingClient#resumeRecording(Bundle)}.
*/
void resumeRecording(@NonNull Bundle params) {
if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
"libstagefright_foundation_headers",
],
+ // TunerService is a system service required for Tuner feature.
+ // TunerJNI is a client of TunerService so we build the dependency here.
+ required: ["mediatuner"],
+
export_include_dirs: ["."],
cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
#include <android/binder_parcel_utils.h>
#include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
using Status = ::ndk::ScopedAStatus;
@@ -37,6 +38,7 @@
} else if (s.isOk()) {
return Result::SUCCESS;
}
+ ALOGE("Aidl exception code %s", s.getDescription().c_str());
return Result::UNKNOWN_ERROR;
}
};
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8b4ca37..f618890 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
namespace android {
@@ -480,7 +481,7 @@
case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
int size = ipAddr.srcIpAddress.v4().size();
srcIpAddress.isIpV6 = false;
- srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+ srcIpAddress.addr.resize(size);
copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
srcIpAddress.addr.begin());
break;
@@ -493,8 +494,6 @@
srcIpAddress.addr.begin());
break;
}
- default:
- break;
}
switch (ipAddr.dstIpAddress.getDiscriminator()) {
case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
dstIpAddress.addr.begin());
break;
}
- default:
- break;
}
}
@@ -696,8 +693,6 @@
getHidlRestartEvent(filterEvents, eventExt);
break;
}
- default:
- break;
}
}
@@ -883,19 +878,18 @@
DemuxFilterEventExt& eventExt) {
auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
eventExt.events.resize(1);
+ DemuxFilterMonitorEvent monitorEvent;
switch (monitor.getTag()) {
case TunerFilterMonitorEvent::scramblingStatus: {
- eventExt.events[0].monitorEvent().scramblingStatus(
- static_cast<ScramblingStatus>(monitor.scramblingStatus));
+ monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+ eventExt.events[0].monitorEvent(monitorEvent);
break;
}
case TunerFilterMonitorEvent::cid: {
- eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+ monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+ eventExt.events[0].monitorEvent(monitorEvent);
break;
}
- default:
- eventExt.events[0].noinit();
- break;
}
}
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..0613223 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
using ::android::hardware::tv::tuner::V1_1::FrontendType;
namespace android {
/////////////// FrontendClient ///////////////////////
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
mTunerFrontend = tunerFrontend;
mAidlCallback = NULL;
mHidlCallback = NULL;
- mId = id;
mType = type;
}
@@ -104,6 +110,11 @@
mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
}
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+ mId = id;
+}
+
Result FrontendClient::tune(const FrontendSettings& settings,
const FrontendSettingsExt1_1& settingsExt1_1) {
if (mTunerFrontend != NULL) {
@@ -333,13 +344,26 @@
}
int FrontendClient::getId() {
- return mId;
+ if (mTunerFrontend != NULL) {
+ Status s = mTunerFrontend->getFrontendId(&mId);
+ if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+ return mId;
+ }
+ ALOGE("Failed to getFrontendId from Tuner Frontend");
+ return -1;
+ }
+
+ if (mFrontend != NULL) {
+ return mId;
+ }
+
+ return -1;
}
vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
vector<FrontendStatus> hidlStatus;
for (TunerFrontendStatus s : aidlStatus) {
- FrontendStatus status;
+ FrontendStatus status = FrontendStatus();
switch (s.getTag()) {
case TunerFrontendStatus::isDemodLocked: {
status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +413,31 @@
}
case TunerFrontendStatus::modulation: {
auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+ FrontendModulationStatus modulation;
switch (mType) {
case (int)FrontendType::DVBC:
- status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBS:
- status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS:
- status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS3:
- status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ status.modulation(modulation);
hidlStatus.push_back(status);
break;
default:
@@ -466,7 +496,7 @@
}
case TunerFrontendStatus::hierarchy: {
status.hierarchy(static_cast<FrontendDvbtHierarchy>(
- s.get<TunerFrontendStatus::freqOffset>()));
+ s.get<TunerFrontendStatus::hierarchy>()));
hidlStatus.push_back(status);
break;
}
@@ -477,15 +507,16 @@
}
case TunerFrontendStatus::plpInfo: {
int size = s.get<TunerFrontendStatus::plpInfo>().size();
- status.plpInfo().resize(size);
+ hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
for (int i = 0; i < size; i++) {
auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
- status.plpInfo()[i] = {
+ info[i] = {
.plpId = (uint8_t)aidlInfo.plpId,
.isLocked = aidlInfo.isLocked,
.uec = (uint32_t)aidlInfo.uec,
};
}
+ status.plpInfo(info);
hidlStatus.push_back(status);
break;
}
@@ -503,52 +534,54 @@
FrontendStatusExt1_1 status;
switch (s.getTag()) {
case TunerFrontendStatus::modulations: {
+ vector<FrontendModulation> ms;
for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
- int size = status.modulations().size();
- status.modulations().resize(size + 1);
+ FrontendModulation m;
switch (mType) {
case (int)FrontendType::DVBC:
- status.modulations()[size].dvbc(
- static_cast<FrontendDvbcModulation>(aidlMod));
+ m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DVBS:
- status.modulations()[size].dvbs(
- static_cast<FrontendDvbsModulation>(aidlMod));
+ m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DVBT:
- status.modulations()[size].dvbt(
- static_cast<FrontendDvbtConstellation>(aidlMod));
+ m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBS:
- status.modulations()[size].isdbs(
- static_cast<FrontendIsdbsModulation>(aidlMod));
+ m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBS3:
- status.modulations()[size].isdbs3(
- static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ISDBT:
- status.modulations()[size].isdbt(
- static_cast<FrontendIsdbtModulation>(aidlMod));
+ m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ATSC:
- status.modulations()[size].atsc(
- static_cast<FrontendAtscModulation>(aidlMod));
+ m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::ATSC3:
- status.modulations()[size].atsc3(
- static_cast<FrontendAtsc3Modulation>(aidlMod));
+ m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+ ms.push_back(m);
break;
case (int)FrontendType::DTMB:
- status.modulations()[size].dtmb(
- static_cast<FrontendDtmbModulation>(aidlMod));
+ m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+ ms.push_back(m);
break;
default:
- status.modulations().resize(size);
break;
}
}
- hidlStatus.push_back(status);
+ if (ms.size() > 0) {
+ status.modulations(ms);
+ hidlStatus.push_back(status);
+ }
break;
}
case TunerFrontendStatus::bers: {
@@ -571,25 +604,31 @@
}
case TunerFrontendStatus::bandwidth: {
auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+ FrontendBandwidth band;
switch (mType) {
case (int)FrontendType::ATSC3:
- status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+ band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBC:
- status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+ band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DVBT:
- status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+ band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+ band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+ band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+ status.bandwidth(band);
hidlStatus.push_back(status);
break;
default:
@@ -599,17 +638,21 @@
}
case TunerFrontendStatus::interval: {
auto aidlInter = s.get<TunerFrontendStatus::interval>();
+ FrontendGuardInterval inter;
switch (mType) {
case (int)FrontendType::DVBT:
- status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+ inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+ inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+ inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+ status.interval(inter);
hidlStatus.push_back(status);
break;
default:
@@ -619,19 +662,21 @@
}
case TunerFrontendStatus::transmissionMode: {
auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+ FrontendTransmissionMode trans;
switch (mType) {
case (int)FrontendType::DVBT:
- status.transmissionMode().dvbt(
- static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+ trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBT:
- status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+ trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
case (int)FrontendType::DTMB:
- status.transmissionMode().dtmb(
- static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+ trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+ status.transmissionMode(trans);
hidlStatus.push_back(status);
break;
default:
@@ -650,28 +695,30 @@
break;
}
case TunerFrontendStatus::interleaving: {
+ vector<FrontendInterleaveMode> modes;
for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
- int size = status.interleaving().size();
- status.interleaving().resize(size + 1);
+ FrontendInterleaveMode mode;
switch (mType) {
case (int)FrontendType::DVBC:
- status.interleaving()[size].dvbc(
- static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+ mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
case (int)FrontendType::ATSC3:
- status.interleaving()[size].atsc3(
- static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+ mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
case (int)FrontendType::DTMB:
- status.interleaving()[size].dtmb(
- static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+ mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+ modes.push_back(mode);
break;
default:
- status.interleaving().resize(size);
break;
}
}
- hidlStatus.push_back(status);
+ if (modes.size() > 0) {
+ status.interleaving(modes);
+ hidlStatus.push_back(status);
+ }
break;
}
case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +737,21 @@
}
case TunerFrontendStatus::rollOff: {
auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+ FrontendRollOff roll;
switch (mType) {
case (int)FrontendType::DVBS:
- status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+ roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS:
- status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+ roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
case (int)FrontendType::ISDBS3:
- status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+ roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+ status.rollOff(roll);
hidlStatus.push_back(status);
break;
default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
struct FrontendClient : public RefBase {
public:
- FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+ FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
~FrontendClient();
/**
@@ -180,6 +180,7 @@
shared_ptr<ITunerFrontend> getAidlFrontend();
+ void setId(int id);
int getId();
private:
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
// Connect with Tuner Service.
::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
mTunerService = ITunerService::fromBinder(binder);
- // TODO: Remove after JNI migration is done.
- mTunerService = NULL;
if (mTunerService == NULL) {
ALOGE("Failed to get tuner service");
} else {
// TODO: b/178124017 update TRM in TunerService independently.
mTunerService->updateTunerResources();
+ mTunerService->getTunerHalVersion(&mTunerVersion);
}
}
@@ -115,7 +114,7 @@
if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
return NULL;
}
- return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+ return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
}
if (mTuner != NULL) {
@@ -127,8 +126,10 @@
if (res != Result::SUCCESS) {
return NULL;
}
- sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+ sp<FrontendClient> frontendClient = new FrontendClient(
+ NULL, (int)hidlInfo.type);
frontendClient->setHidlFrontend(hidlFrontend);
+ frontendClient->setId(id);
return frontendClient;
}
}
@@ -358,7 +359,7 @@
sp<ITuner> TunerClient::getHidlTuner() {
if (mTuner == NULL) {
- mTunerVersion = 0;
+ mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
if (mTuner == NULL) {
ALOGW("Failed to get tuner 1.0 service.");
} else {
- mTunerVersion = 1 << 16;
+ mTunerVersion = TUNER_HAL_VERSION_1_0;
}
} else {
mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
- mTunerVersion = ((1 << 16) | 1);
+ mTunerVersion = TUNER_HAL_VERSION_1_1;
}
}
return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
namespace android {
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
typedef enum {
FRONTEND,
LNB,
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 314bf29..7a18bd5 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -225,6 +225,7 @@
AStorageManager_unmountObb;
ASurfaceControl_create; # introduced=29
ASurfaceControl_createFromWindow; # introduced=29
+ ASurfaceControl_acquire; # introduced=31
ASurfaceControl_release; # introduced=29
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 189be80..c1b5f1d 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -185,10 +185,16 @@
return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
}
-void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+void ASurfaceControl_acquire(ASurfaceControl* aSurfaceControl) {
+ SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- SurfaceControl_release(surfaceControl.get());
+ SurfaceControl_acquire(surfaceControl);
+}
+
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
+ SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+
+ SurfaceControl_release(surfaceControl);
}
ASurfaceTransaction* ASurfaceTransaction_create() {
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
index 9b56b23..f4b46e9 100644
--- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
+++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
@@ -16,12 +16,15 @@
package android.net;
+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.Objects;
/**
@@ -40,10 +43,29 @@
private final long mExpiryTimeMillis;
private final boolean mCaptive;
private final String mVenueFriendlyName;
+ private final int mVenueInfoUrlSource;
+ private final int mTermsAndConditionsSource;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = {
+ CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+ CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT})
+ public @interface CaptivePortalDataSource {}
+
+ /**
+ * Source of information: Other (default)
+ */
+ public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+
+ /**
+ * Source of information: Wi-Fi Passpoint
+ */
+ public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
- String venueFriendlyName) {
+ String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) {
mRefreshTimeMillis = refreshTimeMillis;
mUserPortalUrl = userPortalUrl;
mVenueInfoUrl = venueInfoUrl;
@@ -52,11 +74,14 @@
mExpiryTimeMillis = expiryTimeMillis;
mCaptive = captive;
mVenueFriendlyName = venueFriendlyName;
+ mVenueInfoUrlSource = venueInfoUrlSource;
+ mTermsAndConditionsSource = termsAndConditionsSource;
}
private CaptivePortalData(Parcel p) {
this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
- p.readLong(), p.readLong(), p.readBoolean(), p.readString());
+ p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(),
+ p.readInt());
}
@Override
@@ -74,6 +99,8 @@
dest.writeLong(mExpiryTimeMillis);
dest.writeBoolean(mCaptive);
dest.writeString(mVenueFriendlyName);
+ dest.writeInt(mVenueInfoUrlSource);
+ dest.writeInt(mTermsAndConditionsSource);
}
/**
@@ -88,6 +115,9 @@
private long mExpiryTime = -1;
private boolean mCaptive;
private String mVenueFriendlyName;
+ private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+ private @CaptivePortalDataSource int mUserPortalUrlSource =
+ CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
/**
* Create an empty builder.
@@ -100,8 +130,8 @@
public Builder(@Nullable CaptivePortalData data) {
if (data == null) return;
setRefreshTime(data.mRefreshTimeMillis)
- .setUserPortalUrl(data.mUserPortalUrl)
- .setVenueInfoUrl(data.mVenueInfoUrl)
+ .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource)
+ .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource)
.setSessionExtendable(data.mIsSessionExtendable)
.setBytesRemaining(data.mByteLimit)
.setExpiryTime(data.mExpiryTimeMillis)
@@ -123,7 +153,18 @@
*/
@NonNull
public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
+ return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+ }
+
+ /**
+ * Set the URL to be used for users to login to the portal, if captive, and the source of
+ * the data, see {@link CaptivePortalDataSource}
+ */
+ @NonNull
+ public Builder setUserPortalUrl(@Nullable Uri userPortalUrl,
+ @CaptivePortalDataSource int source) {
mUserPortalUrl = userPortalUrl;
+ mUserPortalUrlSource = source;
return this;
}
@@ -132,7 +173,18 @@
*/
@NonNull
public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
+ return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+ }
+
+ /**
+ * Set the URL that can be used by users to view information about the network venue, and
+ * the source of the data, see {@link CaptivePortalDataSource}
+ */
+ @NonNull
+ public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl,
+ @CaptivePortalDataSource int source) {
mVenueInfoUrl = venueInfoUrl;
+ mVenueInfoUrlSource = source;
return this;
}
@@ -188,7 +240,8 @@
public CaptivePortalData build() {
return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive,
- mVenueFriendlyName);
+ mVenueFriendlyName, mVenueInfoUrlSource,
+ mUserPortalUrlSource);
}
}
@@ -249,6 +302,22 @@
}
/**
+ * Get the information source of the Venue URL
+ * @return The source that the Venue URL was obtained from
+ */
+ public @CaptivePortalDataSource int getVenueInfoUrlSource() {
+ return mVenueInfoUrlSource;
+ }
+
+ /**
+ * Get the information source of the user portal URL
+ * @return The source that the user portal URL was obtained from
+ */
+ public @CaptivePortalDataSource int getUserPortalUrlSource() {
+ return mTermsAndConditionsSource;
+ }
+
+ /**
* Get the venue friendly name
*/
@Nullable
@@ -272,7 +341,8 @@
@Override
public int hashCode() {
return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
- mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName);
+ mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName,
+ mVenueInfoUrlSource, mTermsAndConditionsSource);
}
@Override
@@ -286,7 +356,9 @@
&& mByteLimit == other.mByteLimit
&& mExpiryTimeMillis == other.mExpiryTimeMillis
&& mCaptive == other.mCaptive
- && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName);
+ && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName)
+ && mVenueInfoUrlSource == other.mVenueInfoUrlSource
+ && mTermsAndConditionsSource == other.mTermsAndConditionsSource;
}
@Override
@@ -300,6 +372,8 @@
+ ", expiryTime: " + mExpiryTimeMillis
+ ", captive: " + mCaptive
+ ", venueFriendlyName: " + mVenueFriendlyName
+ + ", venueInfoUrlSource: " + mVenueInfoUrlSource
+ + ", termsAndConditionsSource: " + mTermsAndConditionsSource
+ "}";
}
}
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 252670a..8e1774b 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
svetoslavganov@google.com
-moltmann@google.com
toddke@google.com
suprabh@google.com
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
new file mode 100644
index 0000000..46e2d45
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
new file mode 100644
index 0000000..d9cd590
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
new file mode 100644
index 0000000..e80fd08
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13,8h2v3h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.5,5h2v6h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,3h2v8h-2z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
new file mode 100644
index 0000000..493912b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
new file mode 100644
index 0000000..af677fb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+ android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
new file mode 100644
index 0000000..68b39da
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7556ace..3866151 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1380,7 +1380,7 @@
<!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
<string name="guest_new_guest">Add guest</string>
<!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
- <string name="guest_exit_guest">End guest session</string>
+ <string name="guest_exit_guest">Remove guest</string>
<!-- Name for the guest user [CHAR LIMIT=35] -->
<string name="guest_nickname">Guest</string>
@@ -1485,4 +1485,7 @@
<string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
<!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_ethernet_connected">Ethernet.</string>
+
+ <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_no_calling">No calling.</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index 45028ff..eff9e74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -48,6 +48,8 @@
public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
+ public static final int NO_CALLING = R.string.accessibility_no_calling;
+
public static final int[] ETHERNET_CONNECTION_VALUES = {
R.string.accessibility_ethernet_disconnected,
R.string.accessibility_ethernet_connected,
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 09f285b..14a7cfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -65,7 +65,7 @@
return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
- case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
+ case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
default:
return "unsupported";
@@ -180,7 +180,7 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
TelephonyIcons.NR_5G);
networkToIconLookup.put(toDisplayIconKey(
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
TelephonyIcons.NR_5G_PLUS);
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_NR),
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 4c7b898..0cd5e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -266,7 +266,7 @@
serviceState.getDataRegState()) + ")")
.append(',')
.append("signalStrength=").append(signalStrength == null ? ""
- : signalStrength.toString()).append(',')
+ : signalStrength.getLevel()).append(',')
.append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? ""
: telephonyDisplayInfo.toString()).append(']').toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 0cb9906..e3413aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -317,5 +317,21 @@
ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
}
+
+ public static final int[] WIFI_CALL_STRENGTH_ICONS = {
+ R.drawable.ic_wifi_call_strength_1,
+ R.drawable.ic_wifi_call_strength_1,
+ R.drawable.ic_wifi_call_strength_2,
+ R.drawable.ic_wifi_call_strength_3,
+ R.drawable.ic_wifi_call_strength_3
+ };
+
+ public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
+ R.drawable.ic_mobile_call_strength_1,
+ R.drawable.ic_mobile_call_strength_1,
+ R.drawable.ic_mobile_call_strength_2,
+ R.drawable.ic_mobile_call_strength_3,
+ R.drawable.ic_mobile_call_strength_3
+ };
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 78282fb..841a49e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -39,6 +39,8 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -47,6 +49,8 @@
* Track status of Wi-Fi for the Sys UI.
*/
public class WifiStatusTracker {
+ private static final int HISTORY_SIZE = 32;
+ private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private final Context mContext;
private final WifiNetworkScoreCache mWifiNetworkScoreCache;
private final WifiManager mWifiManager;
@@ -54,6 +58,10 @@
private final ConnectivityManager mConnectivityManager;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Set<Integer> mNetworks = new HashSet<>();
+ // Save the previous HISTORY_SIZE states for logging.
+ private final String[] mHistory = new String[HISTORY_SIZE];
+ // Where to copy the next state into.
+ private int mHistoryIndex;
private final WifiNetworkScoreCache.CacheListener mCacheListener =
new WifiNetworkScoreCache.CacheListener(mHandler) {
@Override
@@ -93,6 +101,13 @@
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
}
+ String log = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append("onCapabilitiesChanged: ")
+ .append("network=").append(network).append(",")
+ .append("networkCapabilities=").append(networkCapabilities)
+ .toString();
+ recordLastWifiNetwork(log);
if (wifiInfo != null) {
updateWifiInfo(wifiInfo);
updateStatusLabel();
@@ -102,6 +117,12 @@
@Override
public void onLost(Network network) {
+ String log = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append("onLost: ")
+ .append("network=").append(network)
+ .toString();
+ recordLastWifiNetwork(log);
if (mNetworks.contains(network.getNetId())) {
mNetworks.remove(network.getNetId());
updateWifiInfo(null);
@@ -336,4 +357,25 @@
}
return null;
}
+
+ private void recordLastWifiNetwork(String log) {
+ mHistory[mHistoryIndex] = log;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
+ }
+
+ /** Dump function. */
+ public void dump(PrintWriter pw) {
+ pw.println(" - WiFi Network History ------");
+ int size = 0;
+ for (int i = 0; i < HISTORY_SIZE; i++) {
+ if (mHistory[i] != null) size++;
+ }
+ // Print out the previous states in ordered number.
+ for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+ i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+ pw.println(" Previous WiFiNetwork("
+ + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+ + mHistory[i & (HISTORY_SIZE - 1)]);
+ }
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab0990..1566b76 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -87,6 +87,7 @@
<!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
<uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
+ <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
<uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
@@ -399,6 +400,7 @@
<uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+ <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 6ba1fcb..34901f5 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@
svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
-moltmann@google.com
toddke@google.com
cbrubaker@google.com
omakoto@google.com
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
index 0831e0e..da079cf0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -103,5 +103,10 @@
default Animator getOutAnimation() {
return null;
}
+
+ /**
+ * Called on orientation changes.
+ */
+ default void onOrientationChange(int orientation) { }
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 7a9e7c7..f3ec39d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -74,7 +74,6 @@
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:textSize="100dp"
- android:includeFontPadding="false"
android:fontFamily="@font/clock"
android:typeface="monospace"
android:elegantTextHeight="false"
@@ -95,7 +94,6 @@
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:textSize="@dimen/large_clock_text_size"
- android:includeFontPadding="false"
android:fontFamily="@font/clock"
android:typeface="monospace"
android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
new file mode 100644
index 0000000..174981e
--- /dev/null
+++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_activated="true"
+ android:color="@color/kg_user_switcher_avatar_background" />
+ <item android:color="@color/kg_user_switcher_avatar_background" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml
new file mode 100644
index 0000000..5644b65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml
@@ -0,0 +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
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:width="@dimen/end_guest_button_border_size"
+ android:color="?android:attr/colorControlHighlight" />
+ <corners android:radius="@dimen/end_guest_button_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
new file mode 100644
index 0000000..addb3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="100"
+ android:viewportHeight="100">
+
+ <path
+ android:fillColor="@color/kg_user_switcher_avatar_background"
+ android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml
new file mode 100644
index 0000000..5c45e83
--- /dev/null
+++ b/packages/SystemUI/res/drawable/toast_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#FFFFFFFF" />
+ <corners android:radius="@dimen/toast_bg_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 416ee81..2789ed1 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -43,17 +43,12 @@
<include layout="@layout/system_icons" />
</FrameLayout>
- <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
- android:layout_width="@dimen/multi_user_switch_width_keyguard"
- android:layout_height="match_parent"
- android:background="@drawable/ripple_drawable"
- android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin">
- <ImageView android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:layout_gravity="center"
- android:scaleType="centerInside"/>
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <ImageView android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
</LinearLayout>
<Space
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 983ba6d..253c03e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,10 +14,50 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container"
- android:visibility="gone"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here -->
-</view>
\ No newline at end of file
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_user_switcher_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+
+ <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
+ android:id="@+id/keyguard_user_switcher_list"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="top|end"
+ android:gravity="end" />
+
+ <LinearLayout
+ android:id="@+id/end_guest_button"
+ android:layout_height="@dimen/end_guest_button_layout_height"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="@dimen/end_guest_button_margin_bottom"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:paddingLeft="@dimen/end_guest_button_padding_horizontal"
+ android:paddingRight="@dimen/end_guest_button_padding_horizontal"
+ android:background="@drawable/end_guest_button_background"
+ android:visibility="gone">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:src="@drawable/ic_exit_to_app"
+ android:background="@android:color/transparent"
+ android:color="?attr/wallpaperTextColor" />
+ <TextView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:gravity="center"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textColor="?attr/wallpaperTextColor"
+ android:textSize="13sp"
+ android:text="@string/guest_exit_button" />
+ </LinearLayout>
+
+</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
deleted file mode 100644
index 4c1042e..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2016 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/keyguard_user_switcher_inner"
- android:orientation="vertical"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
- android:layout_gravity="end"
- android:gravity="end"
- android:paddingTop="4dp">
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 1cd1a04..aaa372a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -19,29 +19,30 @@
<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_marginEnd="8dp"
- android:gravity="center_vertical"
+ android:gravity="end|center_vertical"
android:clickable="true"
- android:background="@drawable/ripple_drawable"
- sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
- sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
- <TextView android:id="@+id/user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="13dp"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
- />
- <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:contentDescription="@null"
- sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
- sysui:framePadding="2.5dp"
- sysui:badgeDiameter="18dp"
- sysui:badgeMargin="1dp"
- sysui:frameColor="@color/kg_user_switcher_rounded_background_color" />
+ android:background="@drawable/kg_user_switcher_rounded_bg"
+ systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+ systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
+ <TextView
+ android:id="@+id/user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="16dp" />
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/user_picture"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameWidth="0dp"
+ systemui:framePadding="0dp"
+ systemui:frameColor="@color/kg_user_avatar_frame" />
</com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 8a47a22..95cee66 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -47,7 +47,7 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="4dp"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:forceHasOverlappingRendering="false"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 6b42705..a4cf5ed 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -48,7 +48,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:gravity="start"
android:textSize="14sp" />
@@ -58,7 +58,7 @@
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:gravity="end"
android:textSize="14sp" />
</FrameLayout>
@@ -120,13 +120,13 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center_vertical|end"
+ android:gravity="center"
+ android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:foreground="@drawable/qs_media_seamless_background"
- android:background="@drawable/qs_media_light_source"
+ android:background="@drawable/qs_media_seamless_background"
android:orientation="horizontal"
android:padding="6dp"
android:contentDescription="@string/quick_settings_media_device_label">
@@ -135,7 +135,7 @@
android:layout_width="@dimen/qs_seamless_icon_size"
android:layout_height="@dimen/qs_seamless_icon_size"
android:layout_gravity="center"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/colorPrimary"
android:src="@*android:drawable/ic_media_seamless" />
<TextView
android:visibility="gone"
@@ -147,7 +147,7 @@
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:text="@*android:string/ext_media_seamless_action"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/colorPrimary"
android:textDirection="locale"
android:textSize="14sp" />
</LinearLayout>
@@ -157,7 +157,7 @@
android:id="@+id/media_seamless_fallback"
android:layout_width="@dimen/qs_seamless_icon_size"
android:layout_height="@dimen/qs_seamless_icon_size"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_cast_connected"
android:forceHasOverlappingRendering="false" />
@@ -171,15 +171,15 @@
android:clickable="true"
android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding"
- android:thumbTint="@color/media_primary_text"
- android:progressTint="@color/media_seekbar_progress"
- android:progressBackgroundTint="@color/media_disabled"
+ android:thumbTint="?android:attr/textColorPrimary"
+ android:progressTint="?android:attr/textColorPrimary"
+ android:progressBackgroundTint="?android:attr/colorBackground"
android:splitTrack="false" />
<!-- App name -->
<TextView
android:id="@+id/app_name"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
@@ -194,7 +194,7 @@
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:singleLine="true"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<!-- Artist name -->
@@ -204,12 +204,12 @@
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
- android:textColor="@color/media_secondary_text"
+ android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp" />
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
- android:tint="@color/media_primary_text"
+ android:tint="?android:attr/textColorPrimary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="6dp" />
@@ -223,7 +223,7 @@
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
android:id="@+id/media_text"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorSecondary"
android:text="@string/controls_media_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -238,7 +238,7 @@
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
- android:textColor="@color/media_primary_text"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_close_session"
app:layout_constraintTop_toBottomOf="@id/media_text"
app:layout_constraintStart_toStartOf="parent"
@@ -262,7 +262,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_settings_button" />
</FrameLayout>
@@ -283,7 +283,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/cancel" />
</FrameLayout>
@@ -304,7 +304,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textColor="@android:color/white"
+ android:textColor="?android:attr/textColorPrimary"
android:text="@string/controls_media_dismiss_button"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d6385ff..859d904 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,12 @@
android:layout_height="match_parent"
android:visibility="gone" />
+ <ViewStub
+ android:id="@+id/keyguard_user_switcher_stub"
+ android:layout="@layout/keyguard_user_switcher"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
<include
layout="@layout/keyguard_status_view"
android:visibility="gone" />
@@ -72,12 +78,6 @@
<include layout="@layout/photo_preview_overlay" />
- <ViewStub
- android:id="@+id/keyguard_user_switcher"
- android:layout="@layout/keyguard_user_switcher"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..de4e062
--- /dev/null
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/toast_width"
+ android:orientation="horizontal"
+ android:background="@drawable/toast_background"
+ android:backgroundTint="?android:attr/colorBackground"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:gravity="center_vertical">
+
+ <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
+ <ImageView
+ android:id="@+id/icon"
+ android:alpha="@dimen/toast_icon_alpha"
+ android:padding="11.5dp"
+ android:layout_width="@dimen/toast_icon_size"
+ android:layout_height="@dimen/toast_icon_size"/>
+ <TextView
+ android:id="@+id/text"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="22dp"
+ android:textSize="@dimen/toast_text_size"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..24c7655 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,4 +52,6 @@
<!-- (footer_height -48dp)/2 -->
<dimen name="controls_management_footer_top_margin">4dp</dimen>
<dimen name="controls_management_favorites_top_margin">8dp</dimen>
+
+ <dimen name="toast_y_offset">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3153d0d..37ec576 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -89,6 +89,8 @@
<color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
<!-- Icon color for selected user avatars in keyguard user switcher -->
<color name="kg_user_switcher_selected_avatar_icon_color">#202124</color>
+ <!-- Color of background circle of user avatars in keyguard user switcher -->
+ <color name="kg_user_switcher_avatar_background">#3C4043</color>
<!-- Icon color for user avatars in quick settings user switcher -->
<color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color>
<!-- Icon color for selected user avatars in quick settings user switcher -->
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 02bd602..ee2b82d 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -23,13 +23,6 @@
<item name="numColumns">4</item>
</style>
- <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
- <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
- <item name="android:textStyle">normal</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">?attr/wallpaperTextColor</item>
- </style>
-
<style name="TextAppearance.QS.UserSwitcher">
<item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8166e35..a1191ae 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -139,6 +139,10 @@
<!-- Size of shadows/elevations on keyguard -->
<attr name="shadowRadius" format="float" />
+ <attr name="handleThickness" format="dimension" />
+ <attr name="handleColor" format="color" />
+ <attr name="scrimColor" format="color" />
+
<!-- Used display CarrierText in Keyguard or QS Footer -->
<declare-styleable name="CarrierText">
<attr name="allCaps" format="boolean" />
@@ -173,15 +177,15 @@
</declare-styleable>
<declare-styleable name="CropView">
- <attr name="handleThickness" format="dimension" />
- <attr name="handleColor" format="color" />
- <attr name="scrimColor" format="color" />
+ <attr name="handleThickness" />
+ <attr name="handleColor" />
+ <attr name="scrimColor" />
</declare-styleable>
<declare-styleable name="MagnifierView">
- <attr name="handleThickness" format="dimension" />
- <attr name="handleColor" format="color" />
- <attr name="scrimColor" format="color" />
+ <attr name="handleThickness" />
+ <attr name="handleColor" />
+ <attr name="scrimColor" />
<attr name="borderThickness" format="dimension" />
<attr name="borderColor" format="color" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5fb6de7..acd671c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -66,9 +66,13 @@
<!-- Color for rounded background for activated user in keyguard user switcher -->
<color name="kg_user_switcher_activated_background_color">#26000000</color>
<!-- Icon color for user avatars in keyguard user switcher -->
- <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
- <!-- Icon color for selected user avatars in keyguard user switcher -->
- <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color>
+ <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color>
+ <!-- Icon color for user avatars in keyguard user switcher that restricted
+ (e.g. cannot be switched to) -->
+ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
+ <!-- Color of background circle of user avatars in keyguard user switcher -->
+ <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color>
+
<!-- Icon color for user avatars in user switcher quick settings -->
<color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
<!-- Icon color for selected user avatars in user switcher quick settings -->
@@ -240,11 +244,8 @@
<color name="magnification_switch_button_color">#7F000000</color>
<!-- media -->
- <color name="media_primary_text">@android:color/white</color>
- <color name="media_secondary_text">#99ffffff</color> <!-- 60% -->
- <color name="media_seekbar_progress">#c0ffffff</color>
<color name="media_disabled">#80ffffff</color>
- <color name="media_seamless_border">#26ffffff</color> <!-- 15% -->
+ <color name="media_seamless_border">?android:attr/colorAccent</color>
<color name="media_divider">#1d000000</color>
<!-- controls -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb04c3b..6196225 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,7 +107,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79471e6..594fbdf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -743,9 +743,6 @@
<!-- end margin for system icons if multi user switch is hidden -->
<dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
- <!-- The thickness of the colored border around the current user. -->
- <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
-
<dimen name="data_usage_graph_marker_width">4dp</dimen>
<!-- The padding bottom of the clock group when QS is expanded. -->
@@ -805,7 +802,7 @@
<!-- Size of user icon + frame in the qs user picker (incl. frame) -->
<dimen name="qs_framed_avatar_size">54dp</dimen>
<!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
- <dimen name="kg_framed_avatar_size">54dp</dimen>
+ <dimen name="kg_framed_avatar_size">32dp</dimen>
<!-- Margin on the left side of the carrier text on Keyguard -->
<dimen name="keyguard_carrier_text_margin">16dp</dimen>
@@ -1324,8 +1321,16 @@
<dimen name="screenrecord_status_icon_height">17.5dp</dimen>
<dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
+ <!-- Keyguard user switcher -->
<dimen name="kg_user_switcher_text_size">16sp</dimen>
+ <!-- End guest session button -->
+ <dimen name="end_guest_button_layout_height">32dp</dimen>
+ <dimen name="end_guest_button_padding_horizontal">16dp</dimen>
+ <dimen name="end_guest_button_margin_bottom">96dp</dimen>
+ <dimen name="end_guest_button_border_size">1dp</dimen>
+ <dimen name="end_guest_button_corner_radius">16dp</dimen>
+
<!-- Opacity at which the background for the shutdown UI will be drawn. -->
<item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
@@ -1354,4 +1359,11 @@
<dimen name="rounded_slider_icon_size">24dp</dimen>
<!-- rounded_slider_icon_size / 2 -->
<dimen name="rounded_slider_icon_inset">12dp</dimen>
+
+ <dimen name="toast_width">296dp</dimen>
+ <item name="toast_icon_alpha" format="float" type="dimen">1</item>
+ <dimen name="toast_text_size">14sp</dimen>
+ <dimen name="toast_y_offset">48dp</dimen>
+ <dimen name="toast_icon_size">48dp</dimen>
+ <dimen name="toast_bg_radius">28dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index ded8a2e..6d731f8 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,11 +18,12 @@
<resources>
<bool name="are_flags_overrideable">false</bool>
- <bool name="flag_notification_pipeline2">false</bool>
+ <bool name="flag_notification_pipeline2">true</bool>
<bool name="flag_notification_pipeline2_rendering">false</bool>
<bool name="flag_notif_updates">false</bool>
<bool name="flag_shade_is_opaque">false</bool>
+ <bool name="flag_monet">false</bool>
<!-- b/171917882 -->
<bool name="flag_notification_twocolumn">false</bool>
@@ -34,9 +35,8 @@
<bool name="flag_brightness_slider">false</bool>
- <!-- The new animations to/from lockscreen and AOD! -->
- <bool name="flag_lockscreen_animations">false</bool>
-
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
+
+ <bool name="flag_toast_style">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index abcf4e8..d997ca2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1112,14 +1112,17 @@
<!-- Name for a freshly added user [CHAR LIMIT=30] -->
<string name="user_new_user_name">New user</string>
+ <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]-->
+ <string name="guest_exit_button">End guest session</string>
+
<!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
- <string name="guest_exit_guest_dialog_title">End guest session?</string>
+ <string name="guest_exit_guest_dialog_title">Remove guest?</string>
<!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
<string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
<!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
- <string name="guest_exit_guest_dialog_remove">End session</string>
+ <string name="guest_exit_guest_dialog_remove">Remove</string>
<!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
<string name="guest_wipe_session_title">Welcome back, guest!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..14b376a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -114,12 +114,12 @@
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
<item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
<item name="android:textStyle">normal</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">?attr/wallpaperTextColor</item>
</style>
<style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
<item name="android:fontWeight">700</item>
- <item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance" />
@@ -582,7 +582,7 @@
<style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
<item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:tint">@android:color/white</item>
+ <item name="android:tint">?android:attr/textColorPrimary</item>
<item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
</style>
@@ -764,6 +764,7 @@
<style name="TextAppearance.PrivacyDialog">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index f834d6d..f83e3a1 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -36,25 +36,23 @@
app:layout_constraintTop_toTopOf="@id/icon"
app:layout_constraintBottom_toBottomOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
/>
<Constraint
android:id="@+id/media_seamless"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/app_name"
+ app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
- android:layout_marginEnd="@dimen/qs_center_guideline_padding"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
/>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index d89e0eb..7c67720 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -36,25 +36,23 @@
app:layout_constraintTop_toTopOf="@id/icon"
app:layout_constraintBottom_toBottomOf="@id/icon"
app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
/>
<Constraint
android:id="@+id/media_seamless"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/app_name"
+ app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
- android:layout_marginEnd="@dimen/qs_center_guideline_padding"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
/>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7f04f28..72e4061 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -32,8 +32,29 @@
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
private final RectF mTmpSourceRectF = new RectF();
+ private final RectF mTmpDestinationRectF = new RectF();
private final Rect mTmpDestinationRect = new Rect();
+ public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRectF.set(destinationBounds);
+ mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+ }
+
+ public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds,
+ float degree, float positionX, float positionY) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRectF.set(destinationBounds);
+ mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ mTmpTransform.postRotate(degree, 0, 0);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setPosition(leash, positionX, positionY);
+ }
+
public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds, Rect insets) {
mTmpSourceRectF.set(sourceBounds);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 186379a..f6b239e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -43,17 +43,6 @@
public static final String TAG = "Task";
- /* Task callbacks */
- @Deprecated
- public interface TaskCallbacks {
- /* Notifies when a task has been bound */
- void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
- /* Notifies when a task has been unbound */
- void onTaskDataUnloaded();
- /* Notifies when a task's windowing mode has changed. */
- void onTaskWindowingModeChanged();
- }
-
/**
* The Task Key represents the unique primary key for the task
*/
@@ -209,12 +198,6 @@
public TaskKey key;
/**
- * The temporary sort index in the stack, used when ordering the stack.
- */
- @Deprecated
- public int temporarySortIndexInStack;
-
- /**
* The icon is the task description icon (if provided), which falls back to the activity icon,
* which can then fall back to the application icon.
*/
@@ -229,45 +212,24 @@
public int colorPrimary;
@ViewDebug.ExportedProperty(category="recents")
public int colorBackground;
- @ViewDebug.ExportedProperty(category="recents")
- @Deprecated
- public boolean useLightOnPrimaryColor;
/**
* The task description for this task, only used to reload task icons.
*/
public TaskDescription taskDescription;
- /**
- * The state isLaunchTarget will be set for the correct task upon launching Recents.
- */
- @ViewDebug.ExportedProperty(category="recents")
- @Deprecated
- public boolean isLaunchTarget;
- @ViewDebug.ExportedProperty(category="recents")
- @Deprecated
- public boolean isStackTask;
- @ViewDebug.ExportedProperty(category="recents")
- @Deprecated
- public boolean isSystemApp;
@ViewDebug.ExportedProperty(category="recents")
public boolean isDockable;
- /**
- * Resize mode. See {@link ActivityInfo#resizeMode}.
- */
- @ViewDebug.ExportedProperty(category="recents")
- @Deprecated
- public int resizeMode;
-
@ViewDebug.ExportedProperty(category="recents")
public ComponentName topActivity;
@ViewDebug.ExportedProperty(category="recents")
public boolean isLocked;
- @Deprecated
- private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
+ // Last snapshot data, only used for recent tasks
+ public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+ new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
public Task() {
// Do nothing
@@ -289,6 +251,15 @@
this.taskDescription = new TaskDescription();
}
+ public Task(Task other) {
+ this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
+ other.isLocked, other.taskDescription, other.topActivity);
+ }
+
+ /**
+ * Use {@link Task#Task(Task)}.
+ */
+ @Deprecated
public Task(TaskKey key, int colorPrimary, int colorBackground,
boolean isDockable, boolean isLocked, TaskDescription taskDescription,
ComponentName topActivity) {
@@ -301,103 +272,6 @@
this.topActivity = topActivity;
}
- @Deprecated
- public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
- String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
- boolean isStackTask, boolean isSystemApp, boolean isDockable,
- TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
- boolean isLocked) {
- this.key = key;
- this.icon = icon;
- this.thumbnail = thumbnail;
- this.title = title;
- this.titleDescription = titleDescription;
- this.colorPrimary = colorPrimary;
- this.colorBackground = colorBackground;
- this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
- Color.WHITE) > 3f;
- this.taskDescription = taskDescription;
- this.isLaunchTarget = isLaunchTarget;
- this.isStackTask = isStackTask;
- this.isSystemApp = isSystemApp;
- this.isDockable = isDockable;
- this.resizeMode = resizeMode;
- this.topActivity = topActivity;
- this.isLocked = isLocked;
- }
-
- /**
- * Copies the metadata from another task, but retains the current callbacks.
- */
- @Deprecated
- public void copyFrom(Task o) {
- this.key = o.key;
- this.icon = o.icon;
- this.thumbnail = o.thumbnail;
- this.title = o.title;
- this.titleDescription = o.titleDescription;
- this.colorPrimary = o.colorPrimary;
- this.colorBackground = o.colorBackground;
- this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
- this.taskDescription = o.taskDescription;
- this.isLaunchTarget = o.isLaunchTarget;
- this.isStackTask = o.isStackTask;
- this.isSystemApp = o.isSystemApp;
- this.isDockable = o.isDockable;
- this.resizeMode = o.resizeMode;
- this.isLocked = o.isLocked;
- this.topActivity = o.topActivity;
- }
-
- /**
- * Add a callback.
- */
- @Deprecated
- public void addCallback(TaskCallbacks cb) {
- if (!mCallbacks.contains(cb)) {
- mCallbacks.add(cb);
- }
- }
-
- /**
- * Remove a callback.
- */
- @Deprecated
- public void removeCallback(TaskCallbacks cb) {
- mCallbacks.remove(cb);
- }
-
- /** Updates the task's windowing mode. */
- @Deprecated
- public void setWindowingMode(int windowingMode) {
- key.setWindowingMode(windowingMode);
- int callbackCount = mCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- mCallbacks.get(i).onTaskWindowingModeChanged();
- }
- }
-
- /** Notifies the callback listeners that this task has been loaded */
- @Deprecated
- public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
- this.icon = applicationIcon;
- this.thumbnail = thumbnailData;
- int callbackCount = mCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
- }
- }
-
- /** Notifies the callback listeners that this task has been unloaded */
- @Deprecated
- public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
- icon = defaultApplicationIcon;
- thumbnail = null;
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).onTaskDataUnloaded();
- }
- }
-
/**
* Returns the top activity component.
*/
@@ -424,9 +298,6 @@
if (!isDockable) {
writer.print(" dockable=N");
}
- if (isLaunchTarget) {
- writer.print(" launchTarget=Y");
- }
if (isLocked) {
writer.print(" locked=Y");
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a5f364d..6fb6760 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,13 +16,9 @@
package com.android.keyguard;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
import android.util.Slog;
import android.view.View;
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -50,13 +46,12 @@
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final KeyguardClockSwitchController mKeyguardClockSwitchController;
- private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final DozeParameters mDozeParameters;
+ private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
- private boolean mKeyguardStatusViewVisibilityAnimating;
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@Inject
@@ -72,11 +67,12 @@
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
mKeyguardClockSwitchController = keyguardClockSwitchController;
- mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
mNotificationIconAreaController = notificationIconAreaController;
mDozeParameters = dozeParameters;
+ mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
+ dozeParameters);
}
@Override
@@ -144,7 +140,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardStatusViewVisibilityAnimating) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
mView.setAlpha(alpha);
}
}
@@ -200,7 +196,7 @@
public void updatePosition(int x, int y, float scale, boolean animate) {
// We animate the status view visible/invisible using Y translation, so don't change it
// while the animation is running.
- if (!mKeyguardStatusViewVisibilityAnimating) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
animate);
}
@@ -230,69 +226,8 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- mView.animate().cancel();
- mKeyguardStatusViewVisibilityAnimating = false;
- if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
- && statusBarState != KEYGUARD) || goingToFullShade) {
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(160)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(
- mAnimateKeyguardStatusViewGoneEndRunnable);
- if (keyguardFadingAway) {
- mView.animate()
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .start();
- }
- } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
- mView.setVisibility(View.VISIBLE);
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.setAlpha(0f);
- mView.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(320)
- .setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (statusBarState == KEYGUARD) {
- if (keyguardFadingAway) {
- mKeyguardStatusViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0)
- .translationYBy(-getHeight() * 0.05f)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
- .setDuration(125)
- .setStartDelay(0)
- .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
- .start();
- } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
- mKeyguardStatusViewVisibilityAnimating = true;
-
- mView.setVisibility(View.VISIBLE);
- mView.setAlpha(0f);
-
- float curTranslationY = mView.getTranslationY();
- mView.setTranslationY(curTranslationY - getHeight() * 0.1f);
- mView.animate()
- .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
- .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- .translationY(curTranslationY)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
- .start();
- } else {
- mView.setVisibility(View.VISIBLE);
- mView.setAlpha(1f);
- }
- } else {
- mView.setVisibility(View.GONE);
- mView.setAlpha(1f);
- }
+ mKeyguardVisibilityHelper.setViewVisibility(
+ statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
}
private void refreshTime() {
@@ -393,19 +328,4 @@
mView.updateLogoutView();
}
};
-
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- mView.setVisibility(View.INVISIBLE);
- };
-
-
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- mView.setVisibility(View.GONE);
- };
-
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
- mKeyguardStatusViewVisibilityAnimating = false;
- };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
new file mode 100644
index 0000000..724e1f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -0,0 +1,137 @@
+/*
+ * 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.keyguard;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
+ * This logic is shared by both the keyguard status view and the keyguard user switcher.
+ */
+public class KeyguardVisibilityHelper {
+
+ private View mView;
+ private final KeyguardStateController mKeyguardStateController;
+ private final DozeParameters mDozeParameters;
+ private boolean mKeyguardViewVisibilityAnimating;
+
+ public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
+ DozeParameters dozeParameters) {
+ mView = view;
+ mKeyguardStateController = keyguardStateController;
+ mDozeParameters = dozeParameters;
+ }
+
+ public boolean isVisibilityAnimating() {
+ return mKeyguardViewVisibilityAnimating;
+ }
+
+ /**
+ * Set the visibility of a keyguard view based on some new state.
+ */
+ public void setViewVisibility(
+ int statusBarState,
+ boolean keyguardFadingAway,
+ boolean goingToFullShade,
+ int oldStatusBarState) {
+ mView.animate().cancel();
+ mKeyguardViewVisibilityAnimating = false;
+ if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+ && statusBarState != KEYGUARD) || goingToFullShade) {
+ mKeyguardViewVisibilityAnimating = true;
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ .setDuration(160)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(
+ mAnimateKeyguardStatusViewGoneEndRunnable);
+ if (keyguardFadingAway) {
+ mView.animate()
+ .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+ .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+ .start();
+ }
+ } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+ mView.setVisibility(View.VISIBLE);
+ mKeyguardViewVisibilityAnimating = true;
+ mView.setAlpha(0f);
+ mView.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setDuration(320)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+ } else if (statusBarState == KEYGUARD) {
+ if (keyguardFadingAway) {
+ mKeyguardViewVisibilityAnimating = true;
+ mView.animate()
+ .alpha(0)
+ .translationYBy(-mView.getHeight() * 0.05f)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setDuration(125)
+ .setStartDelay(0)
+ .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+ .start();
+ } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+ mKeyguardViewVisibilityAnimating = true;
+
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(0f);
+
+ float curTranslationY = mView.getTranslationY();
+ mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f);
+ mView.animate()
+ .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .alpha(1f)
+ .translationY(curTranslationY)
+ .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
+ .start();
+ } else {
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(1f);
+ }
+ } else {
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
+ }
+
+ private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.INVISIBLE);
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.GONE);
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+ mKeyguardViewVisibilityAnimating = false;
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
new file mode 100644
index 0000000..730c14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardUserSwitcher and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardUserSwitcherComponent {
+ /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ KeyguardUserSwitcherComponent build(
+ @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
+ }
+
+ /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
+ KeyguardUserSwitcherController getKeyguardUserSwitcherController();
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index 14d57bf..b9184f4 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.keyguard.dagger;
-parcelable ExternalTimeSuggestion;
+import dagger.Module;
+
+/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+@Module
+public abstract class KeyguardUserSwitcherModule {
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 14d57bf..864472e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -14,6 +14,19 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.keyguard.dagger;
-parcelable ExternalTimeSuggestion;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the KeyguardUserSwitcherComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardUserSwitcherScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index caaee5f..1765627 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,6 @@
import androidx.annotation.StyleRes;
-import com.android.settingslib.Utils;
import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -105,11 +104,6 @@
private DualToneHandler mDualToneHandler;
private int mUser;
- /**
- * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
- */
- private boolean mUseWallpaperTextColors;
-
private int mNonAdaptedSingleToneColor;
private int mNonAdaptedForegroundColor;
private int mNonAdaptedBackgroundColor;
@@ -242,31 +236,6 @@
mIsSubscribedForTunerUpdates = false;
}
- /**
- * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
- * revert back to dark-mode-based/tinted colors.
- *
- * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
- * components
- */
- public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
- if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
- return;
- }
-
- mUseWallpaperTextColors = shouldUseWallpaperTextColor;
-
- if (mUseWallpaperTextColors) {
- updateColors(
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
- Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
- } else {
- updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
- mNonAdaptedSingleToneColor);
- }
- }
-
public void setColorsFromContext(Context context) {
if (context == null) {
return;
@@ -476,13 +445,19 @@
mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
- if (!mUseWallpaperTextColors) {
- updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
- mNonAdaptedSingleToneColor);
- }
+ updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
+ mNonAdaptedSingleToneColor);
}
- private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
+ /**
+ * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
+ * if registered.
+ *
+ * @param foregroundColor
+ * @param backgroundColor
+ * @param singleToneColor
+ */
+ public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
mTextColor = singleToneColor;
if (mBatteryPercentView != null) {
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 247f25e..6b300f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@
}
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
cvh.action(BooleanAction(templateId, !isChecked))
}, true /* blockable */))
@@ -79,7 +80,7 @@
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
val blockable = cvh.usePanel()
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
if (cvh.usePanel()) {
showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@
}
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.action(FloatAction(templateId, newValue))
}, false /* blockable */))
}
override fun longPress(cvh: ControlViewHolder) {
- bouncerOrRun(Action(cvh.cws.ci.controlId, {
+ bouncerOrRun(createAction(cvh.cws.ci.controlId, {
// Long press snould only be called when there is valid control state, otherwise ignore
cvh.cws.control?.let {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@
}
override fun runPendingAction(controlId: String) {
+ if (!keyguardStateController.isUnlocked()) return
if (pendingAction?.controlId == controlId) {
pendingAction?.invoke()
pendingAction = null
@@ -135,7 +137,8 @@
false
}
- private fun bouncerOrRun(action: Action) {
+ @VisibleForTesting
+ fun bouncerOrRun(action: Action) {
if (keyguardStateController.isShowing()) {
var closeDialog = !keyguardStateController.isUnlocked()
if (closeDialog) {
@@ -190,6 +193,10 @@
}
}
+ @VisibleForTesting
+ fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
+ Action(controlId, f, blockable)
+
inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
fun invoke() {
if (!blockable || shouldRunAction(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index f533cfb..db68d17 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -28,11 +28,12 @@
import com.android.systemui.Interpolators
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
+import javax.inject.Inject
/**
* Show the controls space inside a dialog, as from the lock screen.
*/
-class ControlsDialog(
+class ControlsDialog @Inject constructor(
thisContext: Context,
val broadcastDispatcher: BroadcastDispatcher
) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
package com.android.systemui.dagger;
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
import com.android.systemui.wmshell.WMShellModule;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
import dagger.Subcomponent;
/**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ * and {@link TvWMComponent} includes {@link TvWMShellModule}
*/
@WMSingleton
@Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c55fdf4..91cf710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -415,6 +415,7 @@
@Override
public void onUserSwitching(int userId) {
+ if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
// Note that the mLockPatternUtils user has already been updated from setCurrentUser.
// We need to force a reset of the views, since lockNow (called by
// ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -432,6 +433,7 @@
@Override
public void onUserSwitchComplete(int userId) {
+ 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
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 76281d8..9e5b225 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
@@ -61,7 +62,7 @@
/**
* Dagger Module providing {@link StatusBar}.
*/
-@Module(subcomponents = {KeyguardStatusViewComponent.class},
+@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class},
includes = {FalsingModule.class})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index e8dba8f..c1db8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.log
import android.util.Log
-import com.android.systemui.dump.DumpManager
import com.android.systemui.log.dagger.LogModule
import java.io.PrintWriter
import java.text.SimpleDateFormat
@@ -58,7 +57,7 @@
* In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
* the first letter of any of the previous.
*
- * Buffers are provided by [LogModule].
+ * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
*
* @param name The name of this buffer
* @param maxLogs The maximum number of messages to keep in memory at any one time, including the
@@ -77,10 +76,6 @@
var frozen = false
private set
- fun attach(dumpManager: DumpManager) {
- dumpManager.registerBuffer(name, this)
- }
-
/**
* Logs a message to the log buffer
*
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
new file mode 100644
index 0000000..0622df3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.log
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import javax.inject.Inject
+
+@SysUISingleton
+class LogBufferFactory @Inject constructor(
+ private val dumpManager: DumpManager,
+ private val logcatEchoTracker: LogcatEchoTracker
+) {
+ @JvmOverloads
+ fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer {
+ val buffer = LogBuffer(name, maxPoolSize, flexSize, logcatEchoTracker)
+ dumpManager.registerBuffer(name, buffer)
+ return buffer
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index fff185b..19193f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,8 +22,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
import com.android.systemui.log.LogcatEchoTrackerProd;
@@ -40,96 +40,64 @@
@Provides
@SysUISingleton
@DozeLog
- public static LogBuffer provideDozeLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
+ return factory.create("DozeLog", 100);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@SysUISingleton
@NotificationLog
- public static LogBuffer provideNotificationsLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifLog", 1000, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifLog", 1000);
}
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
@NotificationSectionLog
- public static LogBuffer provideNotificationSectionLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifSectionLog", 1000);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@SysUISingleton
@NotifInteractionLog
- public static LogBuffer provideNotifInteractionLogBuffer(
- LogcatEchoTracker echoTracker,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideNotifInteractionLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifInteractionLog", 50);
}
/** Provides a logging buffer for all logs related to Quick Settings. */
@Provides
@SysUISingleton
@QSLog
- public static LogBuffer provideQuickSettingsLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
+ return factory.create("QSLog", 500);
}
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
@Provides
@SysUISingleton
@BroadcastDispatcherLog
- public static LogBuffer provideBroadcastDispatcherLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
+ return factory.create("BroadcastDispatcherLog", 500);
}
/** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
@Provides
@SysUISingleton
@ToastLog
- public static LogBuffer provideToastLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer provideToastLogBuffer(LogBufferFactory factory) {
+ return factory.create("ToastLog", 50);
}
/** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
@Provides
@SysUISingleton
@PrivacyLog
- public static LogBuffer providePrivacyLogBuffer(
- LogcatEchoTracker bufferFilter,
- DumpManager dumpManager) {
- LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
- buffer.attach(dumpManager);
- return buffer;
+ public static LogBuffer providePrivacyLogBuffer(LogBufferFactory factory) {
+ return factory.create("PrivacyLog", 100);
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 9353526..a3ff375 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,7 +1,9 @@
package com.android.systemui.media
+import android.animation.ArgbEvaluator
import android.content.Context
import android.content.Intent
+import android.content.res.ColorStateList
import android.content.res.Configuration
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.util.Log
@@ -112,6 +114,9 @@
private val visualStabilityCallback: VisualStabilityManager.Callback
private var needsReordering: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
+ private var bgColor = getBackgroundColor()
+ private var fgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
private var isRtl: Boolean = false
set(value) {
if (value != field) {
@@ -147,7 +152,7 @@
}
override fun onUiModeChanged() {
- // Only settings button needs to update for dark theme
+ recreatePlayers()
inflateSettingsButton()
}
}
@@ -249,6 +254,11 @@
}
private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
+ data.actions.forEach {
+ it.icon?.setTintList(ColorStateList.valueOf(fgColor))
+ }
+ data.appIcon?.setTintList(ColorStateList.valueOf(fgColor))
+ val dataCopy = data.copy(backgroundColor = bgColor)
val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
@@ -257,14 +267,14 @@
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.view?.player?.setLayoutParams(lp)
- newPlayer.bind(data, key)
+ newPlayer.bind(dataCopy, key)
newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(key, data, newPlayer)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers()
} else {
- existingPlayer.bind(data, key)
- MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+ existingPlayer.bind(dataCopy, key)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer)
if (visualStabilityManager.isReorderingAllowed) {
reorderAllPlayers()
} else {
@@ -298,12 +308,27 @@
}
private fun recreatePlayers() {
+ bgColor = getBackgroundColor()
+
+ fgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
+ pageIndicator.tintList = ColorStateList.valueOf(fgColor)
+
MediaPlayerData.mediaData().forEach { (key, data) ->
removePlayer(key, dismissMediaData = false)
addOrUpdatePlayer(key = key, oldKey = null, data = data)
}
}
+ private fun getBackgroundColor(): Int {
+ val themeAccent = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorAccent).defaultColor
+ val themeBackground = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorBackground).defaultColor
+ // Simulate transparency - cannot be actually transparent because of lockscreen
+ return ArgbEvaluator().evaluate(0.25f, themeBackground, themeAccent) as Int
+ }
+
private fun updatePageIndicator() {
val numPages = mediaContent.getChildCount()
pageIndicator.setNumPages(numPages)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3629d4d..55c55b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -25,7 +25,6 @@
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.Canvas
-import android.graphics.Color
import android.graphics.ImageDecoder
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
@@ -38,7 +37,6 @@
import android.service.notification.StatusBarNotification
import android.text.TextUtils
import android.util.Log
-import com.android.internal.graphics.ColorUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -48,7 +46,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
@@ -68,10 +65,6 @@
private const val TAG = "MediaDataManager"
private const val DEBUG = true
-private const val DEFAULT_LUMINOSITY = 0.25f
-private const val LUMINOSITY_THRESHOLD = 0.05f
-private const val SATURATION_MULTIPLIER = 0.8f
-const val DEFAULT_COLOR = Color.DKGRAY
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@@ -110,6 +103,11 @@
private val useQsMediaPlayer: Boolean
) : Dumpable {
+ private val themeText = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.textColorPrimary).defaultColor
+ private val bgColor = com.android.settingslib.Utils.getColorAttr(context,
+ com.android.internal.R.attr.colorBackground).defaultColor
+
// Internal listeners are part of the internal pipeline. External listeners (those registered
// with [MediaDeviceManager.addListener]) receive events after they have propagated through
// the internal pipeline.
@@ -395,7 +393,6 @@
} else {
null
}
- val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR
val mediaAction = getResumeMediaAction(resumeAction)
foregroundExecutor.execute {
@@ -449,7 +446,6 @@
}
}
}
- val bgColor = computeBackgroundColor(artworkBitmap)
// App name
val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -506,7 +502,7 @@
Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
} else {
action.getIcon()
- }
+ }.setTint(themeText)
val mediaAction = MediaAction(
mediaActionIcon,
runnable,
@@ -589,38 +585,9 @@
}
}
- private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int {
- var color = Color.WHITE
- if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) {
- // If we have valid art, get colors from that
- val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
- .generate()
- val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
- color = swatch.rgb
- } else {
- return DEFAULT_COLOR
- }
- // Adapt background color, so it's always subdued and text is legible
- val tmpHsl = floatArrayOf(0f, 0f, 0f)
- ColorUtils.colorToHSL(color, tmpHsl)
-
- val l = tmpHsl[2]
- // Colors with very low luminosity can have any saturation. This means that changing the
- // luminosity can make a black become red. Let's remove the saturation of very light or
- // very dark colors to avoid this issue.
- if (l < LUMINOSITY_THRESHOLD || l > 1f - LUMINOSITY_THRESHOLD) {
- tmpHsl[1] = 0f
- }
- tmpHsl[1] *= SATURATION_MULTIPLIER
- tmpHsl[2] = DEFAULT_LUMINOSITY
-
- color = ColorUtils.HSLToColor(tmpHsl)
- return color
- }
-
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- Icon.createWithResource(context, R.drawable.lb_ic_play),
+ Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
action,
context.getString(R.string.controls_media_resume)
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 4c96de2..553b6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -163,7 +163,8 @@
}
@Override
- public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+ public void setPlaybackProperties(IBinder token, float volume, boolean looping,
+ boolean hapticGeneratorEnabled) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -171,6 +172,7 @@
if (client != null) {
client.mRingtone.setVolume(volume);
client.mRingtone.setLooping(looping);
+ client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
}
// else no client for token when setting playback properties but will be set at play()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f56a890..7820921 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
@@ -92,9 +93,10 @@
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSlider.Factory brightnessSliderFactory,
- @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+ @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+ FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index b02799f..9426e71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -34,6 +34,8 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.Utils;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -63,6 +65,7 @@
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
+ private final FeatureFlags mFeatureFlags;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
private int mLastOrientation;
@@ -93,11 +96,18 @@
private boolean mUsingHorizontalLayout;
- protected QSPanelControllerBase(T view, QSTileHost host,
+ protected QSPanelControllerBase(
+ T view,
+ QSTileHost host,
QSCustomizerController qsCustomizerController,
- @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager) {
+ @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+ MediaHost mediaHost,
+ MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger,
+ QSLogger qsLogger,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags
+ ) {
super(view);
mHost = host;
mQsCustomizerController = qsCustomizerController;
@@ -107,6 +117,7 @@
mUiEventLogger = uiEventLogger;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -334,9 +345,12 @@
}
boolean shouldUseHorizontalLayout() {
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources())) {
+ return false;
+ }
return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ && getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a0db200..383e932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -30,6 +30,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
import java.util.ArrayList;
import java.util.List;
@@ -57,9 +58,11 @@
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+ DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+ FeatureFlags featureFlags
+ ) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager);
+ uiEventLogger, qsLogger, dumpManager, featureFlags);
mUseSideLabels = qsLabelsFlag;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7776c12..87252ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -29,7 +29,6 @@
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.Pair;
-import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewGroup;
@@ -48,7 +47,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DualToneHandler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.privacy.OngoingPrivacyChip;
@@ -75,7 +73,6 @@
protected QuickQSPanel mHeaderQsPanel;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
- private DualToneHandler mDualToneHandler;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
@@ -110,8 +107,6 @@
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mDualToneHandler = new DualToneHandler(
- new ContextThemeWrapper(context, R.style.QSHeaderTheme));
}
@Override
@@ -149,10 +144,8 @@
}
void onAttach(TintedIconManager iconManager) {
- int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.colorForeground);
- float intensity = getColorIntensity(colorForeground);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
+ int fillColor = Utils.getColorAttrDefaultColor(getContext(),
+ android.R.attr.textColorPrimary);
// Set the correct tint for the status icons so they contrast
iconManager.setTint(fillColor);
@@ -271,14 +264,12 @@
int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
if (textColor != mTextColorPrimary) {
+ int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorSecondary);
mTextColorPrimary = textColor;
mClockView.setTextColor(textColor);
-
- float intensity = getColorIntensity(textColor);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
-
- Rect tintArea = new Rect(0, 0, 0, 0);
- mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
+ mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
+ mTextColorPrimary);
}
updateStatusIconAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 7d8d86f..ae0b5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -28,9 +28,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
-import com.android.systemui.qs.QuickStatusBarHeader;
import java.util.Objects;
@@ -40,10 +38,8 @@
private TextView mCarrierText;
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
- private DualToneHandler mDualToneHandler;
- private ColorStateList mColorForegroundStateList;
- private float mColorForegroundIntensity;
private CellSignalState mLastSignalState;
+ private boolean mProviderModel;
public QSCarrier(Context context) {
super(context);
@@ -64,21 +60,20 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mDualToneHandler = new DualToneHandler(getContext());
- mMobileGroup = findViewById(R.id.mobile_combo);
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+ mProviderModel = true;
} else {
- mMobileRoaming = findViewById(R.id.mobile_roaming);
+ mProviderModel = false;
}
+ mMobileGroup = findViewById(R.id.mobile_combo);
+ mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
- mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
-
- int colorForeground = Utils.getColorAttrDefaultColor(mContext,
- android.R.attr.colorForeground);
- mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
- mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
+ if (mProviderModel) {
+ mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
+ } else {
+ mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+ }
}
/**
@@ -92,26 +87,31 @@
mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
if (state.visible) {
mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
- ColorStateList colorStateList = ColorStateList.valueOf(
- mDualToneHandler.getSingleColor(mColorForegroundIntensity));
+ ColorStateList colorStateList = Utils.getColorAttr(mContext,
+ android.R.attr.textColorPrimary);
mMobileRoaming.setImageTintList(colorStateList);
mMobileSignal.setImageTintList(colorStateList);
- mMobileSignal.setImageLevel(state.mobileSignalIconId);
- StringBuilder contentDescription = new StringBuilder();
- if (state.contentDescription != null) {
- contentDescription.append(state.contentDescription).append(", ");
+ if (mProviderModel) {
+ mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
+ mMobileSignal.setContentDescription(state.contentDescription);
+ } else {
+ mMobileSignal.setImageLevel(state.mobileSignalIconId);
+ StringBuilder contentDescription = new StringBuilder();
+ if (state.contentDescription != null) {
+ contentDescription.append(state.contentDescription).append(", ");
+ }
+ if (state.roaming) {
+ contentDescription
+ .append(mContext.getString(R.string.data_connection_roaming))
+ .append(", ");
+ }
+ // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+ if (hasValidTypeContentDescription(state.typeContentDescription)) {
+ contentDescription.append(state.typeContentDescription);
+ }
+ mMobileSignal.setContentDescription(contentDescription);
}
- if (state.roaming) {
- contentDescription
- .append(mContext.getString(R.string.data_connection_roaming))
- .append(", ");
- }
- // TODO: show mobile data off/no internet text for 5 seconds before carrier text
- if (hasValidTypeContentDescription(state.typeContentDescription)) {
- contentDescription.append(state.typeContentDescription);
- }
- mMobileSignal.setContentDescription(contentDescription);
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 77200cc..a567f51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -19,6 +19,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import android.annotation.MainThread;
+import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
@@ -26,6 +27,7 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -33,6 +35,9 @@
import androidx.annotation.VisibleForTesting;
import com.android.keyguard.CarrierTextController;
+import com.android.settingslib.AccessibilityContentDescriptions;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -62,6 +67,9 @@
new CellSignalState[SIM_SLOTS];
private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+ private int[] mLastSignalLevel = new int[SIM_SLOTS];
+ private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
+ private final boolean mProviderModel;
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@@ -72,6 +80,9 @@
CharSequence typeContentDescription,
CharSequence typeContentDescriptionHtml, CharSequence description,
boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+ if (mProviderModel) {
+ return;
+ }
int slotIndex = getSlotIndex(subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
@@ -92,6 +103,46 @@
}
@Override
+ public void setCallIndicator(NetworkController.IconState statusIcon, int subId) {
+ if (!mProviderModel) {
+ return;
+ }
+ int slotIndex = getSlotIndex(subId);
+ if (slotIndex >= SIM_SLOTS) {
+ Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+ return;
+ }
+ if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+ return;
+ }
+ if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+ if (statusIcon.visible) {
+ mInfos[slotIndex] = new CellSignalState(true,
+ statusIcon.icon, statusIcon.contentDescription, "", false);
+ } else {
+ // Whenever the no Calling & SMS state is cleared, switched to the last
+ // known call strength icon.
+ mInfos[slotIndex] = new CellSignalState(
+ true, mLastSignalLevel[slotIndex],
+ mLastSignalLevelDescription[slotIndex], "", false);
+ }
+ mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+ } else {
+ mLastSignalLevel[slotIndex] = statusIcon.icon;
+ mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
+ // Only Shows the call strength icon when the no Calling & SMS icon is not
+ // shown.
+ if (mInfos[slotIndex].mobileSignalIconId
+ != R.drawable.ic_qs_no_calling_sms) {
+ mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
+ statusIcon.contentDescription, "", false);
+ mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+ }
+ }
+ }
+
+ @Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
for (int i = 0; i < SIM_SLOTS; i++) {
@@ -118,7 +169,12 @@
private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder) {
+ CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+ if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ mProviderModel = true;
+ } else {
+ mProviderModel = false;
+ }
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
@@ -149,7 +205,13 @@
mCarrierDividers[1] = view.getCarrierDivider2();
for (int i = 0; i < SIM_SLOTS; i++) {
- mInfos[i] = new CellSignalState();
+ mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms,
+ context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
+ "", false);
+ mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
+ mLastSignalLevelDescription[i] =
+ context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
+ .toString();
mCarrierGroups[i].setOnClickListener(onClickListener);
}
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -305,16 +367,18 @@
private final Looper mLooper;
private final NetworkController mNetworkController;
private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+ private final Context mContext;
@Inject
public Builder(ActivityStarter activityStarter, @Background Handler handler,
@Main Looper looper, NetworkController networkController,
- CarrierTextController.Builder carrierTextControllerBuilder) {
+ CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
mNetworkController = networkController;
mCarrierTextControllerBuilder = carrierTextControllerBuilder;
+ mContext = context;
}
public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -324,7 +388,7 @@
public QSCarrierGroupController build() {
return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
- mNetworkController, mCarrierTextControllerBuilder);
+ mNetworkController, mCarrierTextControllerBuilder, mContext);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index abf230e..d4bab21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -102,6 +102,12 @@
public void onConfigChanged(Configuration newConfig) {
mView.updateNavBackDrop(newConfig, mLightBarController);
mView.updateResources();
+ if (mTileAdapter.updateNumColumns()) {
+ RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager();
+ if (lm instanceof GridLayoutManager) {
+ ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns());
+ }
+ }
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 21464fd..048fdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -98,7 +98,7 @@
private final UiEventLogger mUiEventLogger;
private final AccessibilityDelegateCompat mAccessibilityDelegate;
private RecyclerView mRecyclerView;
- private final int mNumColumns;
+ private int mNumColumns;
@Inject
public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
@@ -123,6 +123,21 @@
mRecyclerView = null;
}
+ /**
+ * Update the number of columns to show, from resources.
+ *
+ * @return {@code true} if the number of columns changed, {@code false} otherwise
+ */
+ public boolean updateNumColumns() {
+ int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns);
+ if (numColumns != mNumColumns) {
+ mNumColumns = numColumns;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public int getNumColumns() {
return mNumColumns;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 11e6330..6983b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -37,6 +37,7 @@
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.DataSaverTile;
+import com.android.systemui.qs.tiles.DeviceControlsTile;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
@@ -89,6 +90,7 @@
private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
+ private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -123,7 +125,8 @@
Provider<ScreenRecordTile> screenRecordTileProvider,
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
Provider<CameraToggleTile> cameraToggleTileProvider,
- Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
+ Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
+ Provider<DeviceControlsTile> deviceControlsTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -153,6 +156,7 @@
mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
mCameraToggleTileProvider = cameraToggleTileProvider;
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
+ mDeviceControlsTileProvider = deviceControlsTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -212,6 +216,8 @@
return mCameraToggleTileProvider.get();
case "mictoggle":
return mMicrophoneToggleTileProvider.get();
+ case "controls":
+ return mDeviceControlsTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
new file mode 100644
index 0000000..6176a57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DeviceControlsTile @Inject constructor(
+ host: QSHost,
+ @Background backgroundLooper: Looper,
+ @Main mainHandler: Handler,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ private val controlsComponent: ControlsComponent,
+ private val featureFlags: FeatureFlags,
+ private val dialogProvider: Provider<ControlsDialog>,
+ globalSettings: GlobalSettings
+) : QSTileImpl<QSTile.State>(
+ host,
+ backgroundLooper,
+ mainHandler,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+) {
+
+ companion object {
+ const val SETTINGS_FLAG = "controls_lockscreen"
+ }
+
+ private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
+ private var hasControlsApps = AtomicBoolean(false)
+ private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+
+ private var controlsDialog: ControlsDialog? = null
+ private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+
+ private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
+ refreshState()
+ }
+ }
+ }
+
+ init {
+ controlsComponent.getControlsListingController().ifPresent {
+ it.observe(this, listingCallback)
+ }
+ }
+
+ override fun isAvailable(): Boolean {
+ return featureFlags.isKeyguardLayoutEnabled &&
+ controlsLockscreen &&
+ controlsComponent.getControlsUiController().isPresent
+ }
+
+ override fun newTileState(): QSTile.State {
+ return QSTile.State().also {
+ it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps`
+ }
+ }
+
+ override fun handleDestroy() {
+ dismissDialog()
+ super.handleDestroy()
+ }
+
+ private fun createDialog() {
+ if (controlsDialog?.isShowing != true) {
+ controlsDialog = dialogProvider.get()
+ }
+ }
+
+ private fun dismissDialog() {
+ controlsDialog?.dismiss()?.also {
+ controlsDialog = null
+ }
+ }
+
+ override fun handleClick() {
+ if (state.state != Tile.STATE_UNAVAILABLE) {
+ mUiHandler.post {
+ createDialog()
+ controlsDialog?.show(controlsComponent.getControlsUiController().get())
+ }
+ }
+ }
+
+ override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+ state.label = tileLabel
+ state.secondaryLabel = ""
+ state.stateDescription = ""
+ state.contentDescription = state.label
+ state.icon = icon
+ if (hasControlsApps.get()) {
+ state.state = Tile.STATE_ACTIVE
+ if (controlsDialog == null) {
+ mUiHandler.post(this::createDialog)
+ }
+ } else {
+ state.state = Tile.STATE_UNAVAILABLE
+ dismissDialog()
+ }
+ }
+
+ override fun getMetricsCategory(): Int {
+ return 0
+ }
+
+ override fun getLongClickIntent(): Intent {
+ return intent
+ }
+
+ override fun getTileLabel(): CharSequence {
+ return mContext.getText(R.string.quick_controls_title)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 191b85b..0abff77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -469,9 +469,8 @@
if (wifiConnected) {
minimalStateDescription.append(cb.mWifiSignalContentDescription);
minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
- if (!TextUtils.isEmpty(state.secondaryLabel)) {
- minimalContentDescription.append(",").append(state.secondaryLabel);
- }
+ } else if (!TextUtils.isEmpty(state.secondaryLabel)) {
+ minimalContentDescription.append(",").append(state.secondaryLabel);
}
}
state.stateDescription = minimalStateDescription.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6a8c6149..6ca550c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -41,7 +41,7 @@
protected static int layoutResId = R.layout.qs_user_detail_item;
private UserAvatarView mAvatar;
- private TextView mName;
+ protected TextView mName;
private int mActivatedStyle;
private int mRegularStyle;
private View mRestrictedPadlock;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5438743..26781f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -277,12 +277,12 @@
*/
void end() {
mMediaRecorder.stop();
- mMediaProjection.stop();
mMediaRecorder.release();
- mMediaRecorder = null;
- mMediaProjection = null;
mInputSurface.release();
mVirtualDisplay.release();
+ mMediaProjection.stop();
+ mMediaRecorder = null;
+ mMediaProjection = null;
stopInternalAudioRecording();
Log.d(TAG, "end recording");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index c8afd0b..9383aef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,17 +16,22 @@
package com.android.systemui.screenshot;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.android.systemui.R;
@@ -35,6 +40,7 @@
* cropped out.
*/
public class CropView extends View {
+ private static final String TAG = "CropView";
public enum CropBoundary {
NONE, TOP, BOTTOM
}
@@ -118,10 +124,7 @@
case MotionEvent.ACTION_UP:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
// Commit the delta to the stored crop values.
- mTopCrop += mTopDelta;
- mBottomCrop += mBottomDelta;
- mTopDelta = 0;
- mBottomDelta = 0;
+ commitDeltas();
updateListener(event);
}
}
@@ -129,6 +132,42 @@
}
/**
+ * Animate the given boundary to the given value.
+ */
+ public void animateBoundaryTo(CropBoundary boundary, float value) {
+ if (boundary == CropBoundary.NONE) {
+ Log.w(TAG, "No boundary selected for animation");
+ return;
+ }
+ float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
+ : (value - mBottomCrop);
+ ValueAnimator animator = new ValueAnimator();
+ animator.addUpdateListener(animation -> {
+ if (boundary == CropBoundary.TOP) {
+ mTopDelta = animation.getAnimatedFraction() * totalDelta;
+ } else {
+ mBottomDelta = animation.getAnimatedFraction() * totalDelta;
+ }
+ invalidate();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ commitDeltas();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ commitDeltas();
+ }
+ });
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(750);
+ animator.setInterpolator(new FastOutSlowInInterpolator());
+ animator.start();
+ }
+
+ /**
* @return value [0,1] representing the position of the top crop boundary. Does not reflect
* changes from any in-progress touch input.
*/
@@ -148,6 +187,13 @@
mCropInteractionListener = listener;
}
+ private void commitDeltas() {
+ mTopCrop += mTopDelta;
+ mBottomCrop += mBottomDelta;
+ mTopDelta = 0;
+ mBottomDelta = 0;
+ }
+
private void updateListener(MotionEvent event) {
if (mCropInteractionListener != null) {
float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
index 212e6c8..a95c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -70,7 +70,7 @@
RecordingCanvas canvas = mNode.beginRecording(w, h);
canvas.save();
- canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
+ canvas.clipRect(0, 0, mLocation.width(), mLocation.height());
canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
0, 0, null);
canvas.restore();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 20f8451..ae3cd99 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -21,6 +21,8 @@
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.Log;
import androidx.annotation.UiThread;
@@ -32,11 +34,14 @@
* <p>
* To display on-screen, use {@link #getDrawable()}.
*/
-@UiThread
class ImageTileSet {
private static final String TAG = "ImageTileSet";
+ ImageTileSet(@UiThread Handler handler) {
+ mHandler = handler;
+ }
+
interface OnBoundsChangedListener {
/**
* Reports an update to the bounding box that contains all active tiles. These are virtual
@@ -54,6 +59,7 @@
private final List<ImageTile> mTiles = new ArrayList<>();
private final Rect mBounds = new Rect();
+ private final Handler mHandler;
private OnContentChangedListener mOnContentChangedListener;
private OnBoundsChangedListener mOnBoundsChangedListener;
@@ -73,13 +79,32 @@
newBounds.union(newRect);
if (!newBounds.equals(mBounds)) {
mBounds.set(newBounds);
- if (mOnBoundsChangedListener != null) {
- mOnBoundsChangedListener.onBoundsChanged(
- newBounds.left, newBounds.top, newBounds.right, newBounds.bottom);
- }
+ notifyBoundsChanged(mBounds);
}
- if (mOnContentChangedListener != null) {
+ notifyContentChanged();
+ }
+
+ void notifyContentChanged() {
+ if (mOnContentChangedListener == null) {
+ return;
+ }
+ if (mHandler.getLooper().isCurrentThread()) {
mOnContentChangedListener.onContentChanged();
+ } else {
+ mHandler.post(() -> mOnContentChangedListener.onContentChanged());
+ }
+ }
+
+ void notifyBoundsChanged(Rect bounds) {
+ if (mOnBoundsChangedListener == null) {
+ return;
+ }
+ if (mHandler.getLooper().isCurrentThread()) {
+ mOnBoundsChangedListener.onBoundsChanged(
+ bounds.left, bounds.top, bounds.right, bounds.bottom);
+ } else {
+ mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged(
+ bounds.left, bounds.top, bounds.right, bounds.bottom));
}
}
@@ -117,22 +142,16 @@
* getHeight()).
*/
Bitmap toBitmap(Rect bounds) {
+ Log.d(TAG, "exporting with bounds: " + bounds);
if (mTiles.isEmpty()) {
return null;
}
final RenderNode output = new RenderNode("Bitmap Export");
- output.setPosition(0, 0, getWidth(), getHeight());
+ output.setPosition(0, 0, bounds.width(), bounds.height());
RecordingCanvas canvas = output.beginRecording();
- canvas.translate(-getLeft(), -getTop());
- // Additional translation to account for the requested bounds
- canvas.translate(-bounds.left, -bounds.top);
- canvas.clipRect(bounds);
- for (ImageTile tile : mTiles) {
- canvas.save();
- canvas.translate(tile.getLeft(), tile.getTop());
- canvas.drawRenderNode(tile.getDisplayList());
- canvas.restore();
- }
+ Drawable drawable = getDrawable();
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
output.endRecording();
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
@@ -162,14 +181,13 @@
}
void clear() {
- mBounds.set(0, 0, 0, 0);
+ if (mBounds.isEmpty()) {
+ return;
+ }
+ mBounds.setEmpty();
mTiles.forEach(ImageTile::close);
mTiles.clear();
- if (mOnBoundsChangedListener != null) {
- mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0);
- }
- if (mOnContentChangedListener != null) {
- mOnContentChangedListener.onContentChanged();
- }
+ notifyBoundsChanged(mBounds);
+ notifyContentChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index f887151..f8f1d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -144,7 +144,9 @@
setAlpha(0f);
setTranslationX((getParentWidth() - getWidth()) / 2);
setVisibility(View.VISIBLE);
- animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start();
+ boolean touchOnRight = event.getX() > getParentWidth() / 2;
+ float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
+ animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start();
break;
case MotionEvent.ACTION_MOVE:
mLastCropPosition = cropPosition;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index bb07012..d56c806 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -50,8 +50,6 @@
public class ScrollCaptureClient {
private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024);
private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed
- private static final int MAX_PAGES = 5;
- private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE;
@VisibleForTesting
static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
@@ -66,10 +64,11 @@
/**
* Session start should be deferred until UI is active because of resource allocation and
* potential visible side effects in the target window.
-
+ *
* @param sessionConsumer listener to receive the session once active
+ * @param maxPages the capture buffer size expressed as a multiple of the content height
*/
- void start(Consumer<Session> sessionConsumer);
+ void start(Consumer<Session> sessionConsumer, float maxPages);
/**
* Close the connection.
@@ -196,6 +195,7 @@
private int mTileWidth;
private Rect mRequestRect;
private boolean mStarted;
+ private int mMaxTiles;
private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
mConnectionConsumer = connectionConsumer;
@@ -285,12 +285,15 @@
// ScrollCaptureController.Connection
@Override
- public void start(Consumer<Session> sessionConsumer) {
+ public void start(Consumer<Session> sessionConsumer, float maxPages) {
if (DEBUG_SCROLL) {
- Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")");
+ Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
+ + " maxPages=" + maxPages + ")"
+ + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]");
}
+ mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
- MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
mSessionConsumer = sessionConsumer;
try {
mConnection.startCapture(mReader.getSurface());
@@ -345,7 +348,7 @@
@Override
public int getMaxTiles() {
- return MAX_IMAGE_COUNT;
+ return mMaxTiles;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 25438a6..d97f644 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -34,6 +35,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -44,13 +46,23 @@
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Interaction controller between the UI and ScrollCaptureClient.
*/
public class ScrollCaptureController implements OnComputeInternalInsetsListener {
private static final String TAG = "ScrollCaptureController";
+ private static final float MAX_PAGES_DEFAULT = 3f;
+
+ private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
+
+ private static final int UP = -1;
+ private static final int DOWN = 1;
+
+ private int mDirection = DOWN;
+ private boolean mAtBottomEdge;
+ private boolean mAtTopEdge;
+ private Session mSession;
// TODO: Support saving without additional action.
private enum PendingAction {
@@ -59,7 +71,6 @@
SAVE
}
- public static final int MAX_PAGES = 5;
public static final int MAX_HEIGHT = 12000;
private final Connection mConnection;
@@ -91,7 +102,7 @@
mBgExecutor = bgExecutor;
mImageExporter = exporter;
mUiEventLogger = uiEventLogger;
- mImageTileSet = new ImageTileSet();
+ mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
}
/**
@@ -129,7 +140,9 @@
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
- mConnection.start(this::startCapture);
+ float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
+ SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
+ mConnection.start(this::startCapture, maxPages);
}
@@ -232,41 +245,82 @@
return mWindow.findViewById(res);
}
+
+ private void onCaptureResult(CaptureResult result) {
+ Log.d(TAG, "onCaptureResult: " + result);
+ boolean emptyResult = result.captured.height() == 0;
+ boolean partialResult = !emptyResult
+ && result.captured.height() < result.requested.height();
+ boolean finish = false;
+
+ if (partialResult) {
+ // Potentially reached a vertical boundary. Extend in the other direction.
+ switch (mDirection) {
+ case DOWN:
+ Log.d(TAG, "Reached bottom edge.");
+ mAtBottomEdge = true;
+ mDirection = UP;
+ break;
+ case UP:
+ Log.d(TAG, "Reached top edge.");
+ mAtTopEdge = true;
+ mDirection = DOWN;
+ break;
+ }
+
+ if (mAtTopEdge && mAtBottomEdge) {
+ Log.d(TAG, "Reached both top and bottom edge, ending.");
+ finish = true;
+ } else {
+ // only reverse if the edge was relatively close to the starting point
+ if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) {
+ Log.d(TAG, "Restarting in reverse direction.");
+
+ // Because of temporary limitations, we cannot just jump to the opposite edge
+ // and continue there. Instead, clear the results and start over capturing from
+ // here in the other direction.
+ mImageTileSet.clear();
+ } else {
+ Log.d(TAG, "Capture is tall enough, stopping here.");
+ finish = true;
+ }
+ }
+ }
+
+ if (!emptyResult) {
+ mImageTileSet.addTile(new ImageTile(result.image, result.captured));
+ }
+
+ Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
+ + " - " + mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
+ + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+
+
+ // Stop when "too tall"
+ if (mImageTileSet.size() >= mSession.getMaxTiles()
+ || mImageTileSet.getHeight() > MAX_HEIGHT) {
+ Log.d(TAG, "Max height and/or tile count reached.");
+ finish = true;
+ }
+
+ if (finish) {
+ Session session = mSession;
+ mSession = null;
+ Log.d(TAG, "Stop.");
+ mUiExecutor.execute(() -> afterCaptureComplete(session));
+ return;
+ }
+
+ int nextTop = (mDirection == DOWN) ? result.captured.bottom
+ : result.captured.top - mSession.getTileHeight();
+ Log.d(TAG, "requestTile: " + nextTop);
+ mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
+ }
+
private void startCapture(Session session) {
- Log.d(TAG, "startCapture");
- Consumer<ScrollCaptureClient.CaptureResult> consumer =
- new Consumer<ScrollCaptureClient.CaptureResult>() {
-
- int mFrameCount = 0;
- int mTop = 0;
-
- @Override
- public void accept(ScrollCaptureClient.CaptureResult result) {
- mFrameCount++;
-
- boolean emptyFrame = result.captured.height() == 0;
- if (!emptyFrame) {
- ImageTile tile = new ImageTile(result.image, result.captured);
- Log.d(TAG, "Adding tile: " + tile);
- mImageTileSet.addTile(tile);
- Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", "
- + "h=" + mImageTileSet.getHeight());
- }
-
- if (emptyFrame || mFrameCount >= MAX_PAGES
- || mTop + session.getTileHeight() > MAX_HEIGHT) {
-
- mUiExecutor.execute(() -> afterCaptureComplete(session));
- return;
- }
- mTop += result.captured.height();
- session.requestTile(mTop, /* consumer */ this);
- }
- };
-
- // fire it up!
- session.requestTile(0, consumer);
- };
+ mSession = session;
+ session.requestTile(0, this::onCaptureResult);
+ }
@UiThread
void afterCaptureComplete(Session session) {
@@ -277,6 +331,7 @@
} else {
mPreview.setImageDrawable(mImageTileSet.getDrawable());
mMagnifierView.setImageTileset(mImageTileSet);
+ mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
index 72f489b..4ec8eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
@@ -56,7 +56,7 @@
mNode = new RenderNode("TiledImageDrawable");
}
mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight());
- Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight());
+ Canvas canvas = mNode.beginRecording();
// Align content (virtual) top/left with 0,0, within the render node
canvas.translate(-mTiles.getLeft(), -mTiles.getTop());
for (int i = 0; i < mTiles.size(); i++) {
@@ -79,8 +79,8 @@
if (canvas.isHardwareAccelerated()) {
Rect bounds = getBounds();
canvas.save();
- canvas.clipRect(bounds);
- canvas.translate(bounds.left, bounds.top);
+ canvas.clipRect(0, 0, bounds.width(), bounds.height());
+ canvas.translate(-bounds.left, -bounds.top);
canvas.drawRenderNode(mNode);
canvas.restore();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
import android.util.Log;
import android.util.MathUtils;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2dd85e9..7aa41e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -67,11 +67,15 @@
return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
}
- public boolean useNewLockscreenAnimations() {
- return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
- }
-
public boolean isPeopleTileEnabled() {
return mFlagReader.isEnabled(R.bool.flag_conversations);
}
+
+ public boolean isToastStyleEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_toast_style);
+ }
+
+ public boolean isMonetEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_monet);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index c1feaca..2f0f90d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,10 +11,14 @@
import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
+import android.os.SystemProperties
import android.util.AttributeSet
import android.view.View
import com.android.systemui.Interpolators
+val enableLightReveal =
+ SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false)
+
/**
* Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
* 100% of the view(s) underneath the scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 0465ebf..edcf6d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -18,23 +18,19 @@
import static android.service.notification.NotificationListenerService.Ranking;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK;
+
import android.app.NotificationManager;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.util.Pair;
-import androidx.annotation.Nullable;
-
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
@@ -45,10 +41,10 @@
* should show an indicator.
*/
@SysUISingleton
-public class AssistantFeedbackController extends ContentObserver {
- private final Uri FEEDBACK_URI
- = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
- private ContentResolver mResolver;
+public class AssistantFeedbackController {
+ private final Context mContext;
+ private final Handler mHandler;
+ private final DeviceConfigProxy mDeviceConfigProxy;
public static final int STATUS_UNCHANGED = 0;
public static final int STATUS_ALERTED = 1;
@@ -56,34 +52,39 @@
public static final int STATUS_PROMOTED = 3;
public static final int STATUS_DEMOTED = 4;
- private boolean mFeedbackEnabled;
+ private volatile boolean mFeedbackEnabled;
+
+ private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) {
+ mFeedbackEnabled = properties.getBoolean(
+ ENABLE_NAS_FEEDBACK, false);
+ }
+ }
+ };
/** Injected constructor */
@Inject
- public AssistantFeedbackController(Context context) {
- super(new Handler(Looper.getMainLooper()));
- mResolver = context.getContentResolver();
- mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL);
- update(null);
+ public AssistantFeedbackController(@Main Handler handler,
+ Context context, DeviceConfigProxy proxy) {
+ mHandler = handler;
+ mContext = context;
+ mDeviceConfigProxy = proxy;
+ mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ENABLE_NAS_FEEDBACK, false);
+ mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ this::postToHandler, mPropertiesChangedListener);
}
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri, int flags) {
- update(uri);
- }
-
- @VisibleForTesting
- public void update(@Nullable Uri uri) {
- if (uri == null || FEEDBACK_URI.equals(uri)) {
- mFeedbackEnabled = Settings.Global.getInt(mResolver,
- Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0)
- != 0;
- }
+ private void postToHandler(Runnable r) {
+ this.mHandler.post(r);
}
/**
- * Determines whether to show any user controls related to the assistant. This is based on the
- * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED}
+ * Determines whether to show any user controls related to the assistant based on the
+ * DeviceConfig flag value
*/
public boolean isFeedbackEnabled() {
return mFeedbackEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 85d8df8..8c2fa33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -29,7 +29,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
@@ -55,7 +54,6 @@
private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
private final Resources mResources;
private final BatteryController mBatteryController;
- private final FeatureFlags mFeatureFlags;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
@@ -67,8 +65,7 @@
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
PowerManager powerManager,
BatteryController batteryController,
- TunerService tunerService,
- FeatureFlags featureFlags) {
+ TunerService tunerService) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -77,7 +74,6 @@
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
mPowerManager = powerManager;
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
- mFeatureFlags = featureFlags;
tunerService.addTunable(
this,
@@ -204,7 +200,8 @@
* then abruptly showing AOD.
*/
public boolean shouldControlUnlockedScreenOff() {
- return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
+ return getAlwaysOn() && SystemProperties.getBoolean(
+ "persist.sysui.show_new_screen_on_transitions", false);
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 57a64e4..e0df4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -55,6 +56,12 @@
private int mKeyguardStatusHeight;
/**
+ * Height of {@link KeyguardUserSwitcherListView} when it
+ * is closed and only the current user's icon is visible.
+ */
+ private int mKeyguardUserSwitcherHeight;
+
+ /**
* Preferred Y position of clock.
*/
private int mClockPreferredY;
@@ -173,17 +180,20 @@
* Sets up algorithm values.
*/
public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
- float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
- boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
- boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
- float qsExpansion, int cutoutTopInset) {
+ float panelExpansion, int parentHeight, int keyguardStatusHeight,
+ int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
+ boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
+ int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
+ int cutoutTopInset) {
mMinTopMargin = statusBarMinHeight + (showLockIcon
- ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon);
+ ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
+ + keyguardUserSwitcherHeight;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight;
+ mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
mClockPreferredY = clockPreferredY;
mHasCustomClock = hasCustomClock;
mHasVisibleNotifs = hasVisibleNotifs;
@@ -246,7 +256,8 @@
final int availableHeight = mMaxShadeBottom - mMinTopMargin;
final int containerCenter = mMinTopMargin + availableHeight / 2;
- float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
+ float y = containerCenter
+ - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
- mClockNotificationsMargin - mNotificationStackHeight / 2;
if (y < mMinTopMargin) {
y = mMinTopMargin;
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 5f547b5..33798d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import android.annotation.ColorInt;
@@ -25,6 +26,7 @@
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;
@@ -45,18 +47,15 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -75,18 +74,16 @@
private boolean mShowPercentAvailable;
private boolean mBatteryCharging;
- private boolean mKeyguardUserSwitcherShowing;
private boolean mBatteryListening;
private TextView mCarrierLabel;
- private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
private BatteryController mBatteryController;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private UserSwitcherController mUserSwitcherController;
+ private boolean mKeyguardUserSwitcherEnabled;
+ private final UserManager mUserManager;
private int mSystemIconsSwitcherHiddenExpandedMargin;
private int mSystemIconsBaseMargin;
@@ -109,13 +106,13 @@
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mUserManager = UserManager.get(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSystemIconsContainer = findViewById(R.id.system_icons_container);
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -124,7 +121,6 @@
mStatusIconContainer = findViewById(R.id.statusIcons);
loadDimens();
- updateUserSwitcher();
mBatteryController = Dependency.get(BatteryController.class);
}
@@ -137,14 +133,6 @@
R.dimen.multi_user_avatar_keyguard_size);
mMultiUserAvatar.setLayoutParams(lp);
- // Multi-user switch
- lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(
- R.dimen.multi_user_switch_width_keyguard);
- lp.setMarginEnd(getResources().getDimensionPixelSize(
- R.dimen.multi_user_switch_keyguard_margin));
- mMultiUserSwitch.setLayoutParams(lp);
-
// System icons
lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
lp.setMarginStart(getResources().getDimensionPixelSize(
@@ -194,22 +182,28 @@
}
private void updateVisibilities() {
- if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
- if (mMultiUserSwitch.getParent() != null) {
- getOverlay().remove(mMultiUserSwitch);
+ if (mMultiUserAvatar.getParent() != mStatusIconArea
+ && !mKeyguardUserSwitcherEnabled) {
+ if (mMultiUserAvatar.getParent() != null) {
+ getOverlay().remove(mMultiUserAvatar);
}
- mStatusIconArea.addView(mMultiUserSwitch, 0);
- } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
- mStatusIconArea.removeView(mMultiUserSwitch);
+ mStatusIconArea.addView(mMultiUserAvatar, 0);
+ } else if (mMultiUserAvatar.getParent() == mStatusIconArea
+ && mKeyguardUserSwitcherEnabled) {
+ mStatusIconArea.removeView(mMultiUserAvatar);
}
- if (mKeyguardUserSwitcher == null) {
+ if (!mKeyguardUserSwitcherEnabled) {
// 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.
- if (mMultiUserSwitch.isMultiUserEnabled()) {
- mMultiUserSwitch.setVisibility(View.VISIBLE);
+ // 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) {
+ mMultiUserAvatar.setVisibility(View.VISIBLE);
} else {
- mMultiUserSwitch.setVisibility(View.GONE);
+ mMultiUserAvatar.setVisibility(View.GONE);
}
}
mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
@@ -220,11 +214,12 @@
(LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
// If the avatar icon is gone, we need to have some end margin to display the system icons
// correctly.
- int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+ int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
? mSystemIconsBaseMargin
: 0;
- int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
- baseMarginEnd;
+ int marginEnd =
+ mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
+ : baseMarginEnd;
marginEnd = calculateMargin(marginEnd, mPadding.second);
if (marginEnd != lp.getMarginEnd()) {
lp.setMarginEnd(marginEnd);
@@ -334,20 +329,11 @@
}
}
- private void updateUserSwitcher() {
- boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
- mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
- mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
- mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
- }
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
UserInfoController userInfoController = Dependency.get(UserInfoController.class);
userInfoController.addCallback(this);
- mUserSwitcherController = Dependency.get(UserSwitcherController.class);
- mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
@@ -369,11 +355,6 @@
mMultiUserAvatar.setImageDrawable(picture);
}
- /** */
- public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) {
- mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer);
- }
-
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
if (mBatteryCharging != charging) {
@@ -387,54 +368,42 @@
// could not care less
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
- updateUserSwitcher();
- }
-
- public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
- mKeyguardUserSwitcherShowing = showing;
- if (animate) {
- animateNextLayoutChange();
- }
- updateVisibilities();
- updateLayoutConsideringCutout();
- updateSystemIconsLayoutParams();
+ public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ mKeyguardUserSwitcherEnabled = enabled;
}
private void animateNextLayoutChange() {
final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
- final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
+ final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
- boolean userSwitcherHiding = userSwitcherVisible
- && mMultiUserSwitch.getParent() != mStatusIconArea;
+ boolean userAvatarHiding = userAvatarVisible
+ && mMultiUserAvatar.getParent() != mStatusIconArea;
mSystemIconsContainer.setX(systemIconsCurrentX);
mSystemIconsContainer.animate()
.translationX(0)
.setDuration(400)
- .setStartDelay(userSwitcherHiding ? 300 : 0)
+ .setStartDelay(userAvatarHiding ? 300 : 0)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.start();
- if (userSwitcherHiding) {
- getOverlay().add(mMultiUserSwitch);
- mMultiUserSwitch.animate()
+ if (userAvatarHiding) {
+ getOverlay().add(mMultiUserAvatar);
+ mMultiUserAvatar.animate()
.alpha(0f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
.withEndAction(() -> {
- mMultiUserSwitch.setAlpha(1f);
- getOverlay().remove(mMultiUserSwitch);
+ mMultiUserAvatar.setAlpha(1f);
+ getOverlay().remove(mMultiUserAvatar);
})
.start();
} else {
- mMultiUserSwitch.setAlpha(0f);
- mMultiUserSwitch.animate()
+ mMultiUserAvatar.setAlpha(0f);
+ mMultiUserAvatar.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(200)
@@ -452,8 +421,8 @@
if (visibility != View.VISIBLE) {
mSystemIconsContainer.animate().cancel();
mSystemIconsContainer.setTranslationX(0);
- mMultiUserSwitch.animate().cancel();
- mMultiUserSwitch.setAlpha(1f);
+ mMultiUserAvatar.animate().cancel();
+ mMultiUserAvatar.setAlpha(1f);
} else {
updateVisibilities();
updateSystemIconsLayoutParams();
@@ -523,9 +492,9 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryCharging: " + mBatteryCharging);
- pw.println(" mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing);
pw.println(" mBatteryListening: " + mBatteryListening);
pw.println(" mLayoutState: " + mLayoutState);
+ pw.println(" mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
if (mBatteryView != null) {
mBatteryView.dump(fd, pw, args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 480d3f4..d9cb9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -35,7 +35,6 @@
import com.android.systemui.R;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSDetailDisplayer;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
@@ -44,8 +43,6 @@
public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
protected QSDetailDisplayer mQSDetailDisplayer;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private boolean mKeyguardMode;
private UserSwitcherController.BaseUserAdapter mUserListener;
final UserManager mUserManager;
@@ -85,15 +82,6 @@
refreshContentDescription();
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
- public void setKeyguardMode(boolean keyguardShowing) {
- mKeyguardMode = keyguardShowing;
- registerListener();
- }
-
public boolean isMultiUserEnabled() {
// TODO(b/138661450) Move IPC calls to background
return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
@@ -123,11 +111,7 @@
@Override
public void onClick(View v) {
- if (mKeyguardMode) {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.show(true /* animate */);
- }
- } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
+ if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
View center = getChildCount() > 0 ? getChildAt(0) : this;
int[] tmpInt = new int[2];
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 e0ef3b6..3b09eda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -48,6 +48,7 @@
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.UserManager;
import android.util.Log;
import android.util.MathUtils;
import android.view.DisplayCutout;
@@ -57,6 +58,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
@@ -76,6 +78,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -95,7 +98,6 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
@@ -132,8 +134,10 @@
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Utils;
import com.android.wm.shell.animation.FlingAnimationUtils;
import java.io.FileDescriptor;
@@ -283,6 +287,22 @@
}
};
+ final KeyguardUserSwitcherController.KeyguardUserSwitcherListener
+ mKeyguardUserSwitcherListener =
+ new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
+ @Override
+ public void onKeyguardUserSwitcherChanged(boolean open) {
+ if (mKeyguardUserSwitcherController != null
+ && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+ return;
+ }
+
+ updateUserSwitcherVisibility(open
+ && mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isKeyguardFadingAway());
+ }
+ };
+
private final LayoutInflater mLayoutInflater;
private final PowerManager mPowerManager;
private final AccessibilityManager mAccessibilityManager;
@@ -295,7 +315,7 @@
private final MediaHierarchyManager mMediaHierarchyManager;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- private final QSDetailDisplayer mQSDetailDisplayer;
+ private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
private final FeatureFlags mFeatureFlags;
private final ScrimController mScrimController;
private final ControlsComponent mControlsComponent;
@@ -307,7 +327,7 @@
private int mMaxAllowedKeyguardNotifications;
private KeyguardAffordanceHelper mAffordanceHelper;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private ViewGroup mBigClockContainer;
private QS mQs;
@@ -332,6 +352,7 @@
private boolean mQsExpandedWhenExpandingStarted;
private boolean mQsFullyExpanded;
private boolean mKeyguardShowing;
+ private boolean mKeyguardUserSwitcherEnabled;
private boolean mDozing;
private boolean mDozingOnDown;
private int mBarState;
@@ -464,6 +485,7 @@
private final CommandQueue mCommandQueue;
private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final UserManager mUserManager;
private final ShadeController mShadeController;
private final MediaDataManager mMediaDataManager;
private int mDisplayId;
@@ -556,11 +578,12 @@
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+ KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
AuthController authController,
- QSDetailDisplayer qsDetailDisplayer,
ScrimController scrimController,
+ UserManager userManager,
MediaDataManager mediaDataManager,
AmbientState ambientState,
FeatureFlags featureFlags,
@@ -581,8 +604,10 @@
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
- mQSDetailDisplayer = qsDetailDisplayer;
mFeatureFlags = featureFlags;
+ mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+ mKeyguardUserSwitcherEnabled = mResources.getBoolean(
+ com.android.internal.R.bool.config_keyguardUserSwitcher);
mView.setWillNotDraw(!DEBUG);
mLayoutInflater = layoutInflater;
mFalsingManager = falsingManager;
@@ -598,6 +623,7 @@
mDozeParameters = dozeParameters;
mBiometricUnlockController = biometricUnlockController;
mScrimController = scrimController;
+ mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mControlsComponent = controlsComponent;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -660,9 +686,17 @@
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
+
+ KeyguardUserSwitcherView keyguardUserSwitcherView = null;
+
+ if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
+ ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+ keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+ }
+
+ updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+ keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
R.id.notification_stack_scroller);
@@ -735,7 +769,8 @@
R.dimen.heads_up_status_bar_padding);
}
- private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+ private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+ KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
@@ -746,6 +781,28 @@
KeyguardClockSwitchController keyguardClockSwitchController =
statusViewComponent.getKeyguardClockSwitchController();
keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+
+ if (mKeyguardUserSwitcherController != null) {
+ // Try to close the switcher so that callbacks are triggered if necessary.
+ // Otherwise, NPV can get into a state where some of the views are still hidden
+ mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
+ mKeyguardUserSwitcherController.removeCallback();
+ }
+
+ // Re-associate the KeyguardUserSwitcherController
+ if (keyguardUserSwitcherView != null) {
+ KeyguardUserSwitcherComponent userSwitcherComponent =
+ mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
+
+ mKeyguardUserSwitcherController =
+ userSwitcherComponent.getKeyguardUserSwitcherController();
+ mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
+ mKeyguardUserSwitcherController.init();
+ mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+ } else {
+ mKeyguardUserSwitcherController = null;
+ mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+ }
}
/**
@@ -783,18 +840,13 @@
mNotificationStackScrollLayoutController.setLayoutParams(lp);
}
- if (shouldUseSplitNotificationShade()) {
+ if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
// In order to change the constraints at runtime, all children of the Constraint Layout
// must have ids.
ensureAllViewsHaveIds(mNotificationContainerParent);
}
}
- private boolean shouldUseSplitNotificationShade() {
- return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
- && mResources.getBoolean(R.bool.config_use_split_notification_shade);
- }
-
private static void ensureAllViewsHaveIds(ViewGroup parentView) {
for (int i = 0; i < parentView.getChildCount(); i++) {
View childView = parentView.getChildAt(i);
@@ -805,6 +857,7 @@
}
private void reInflateViews() {
+ if (DEBUG) Log.d(TAG, "reInflateViews");
// Re-inflate the status view group.
KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
int index = mView.indexOfChild(keyguardStatusView);
@@ -813,8 +866,27 @@
R.layout.keyguard_status_view, mView, false);
mView.addView(keyguardStatusView, index);
+ // Re-inflate the keyguard user switcher group.
+ boolean showUserSwitcher =
+ mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
+ KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
+ R.id.keyguard_user_switcher_view);
+ if (keyguardUserSwitcherView != null) {
+ index = mView.indexOfChild(keyguardUserSwitcherView);
+ mView.removeView(keyguardUserSwitcherView);
+ if (showUserSwitcher) {
+ keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
+ R.layout.keyguard_user_switcher, mView, false);
+ mView.addView(keyguardUserSwitcherView, index);
+ }
+ } else if (showUserSwitcher) {
+ // It's possible the user switcher was never inflated if the configuration changed
+ ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+ keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+ }
+
mBigClockContainer.removeAllViews();
- updateViewControllers(keyguardStatusView);
+ updateViewControllers(keyguardStatusView, keyguardUserSwitcherView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
@@ -838,6 +910,13 @@
false,
false,
mBarState);
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
+ }
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -942,6 +1021,8 @@
? mKeyguardStatusViewController.getHeight()
: (int) (mKeyguardStatusViewController.getHeight()
- mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
+ mKeyguardUserSwitcherController == null
+ ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
clockPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
@@ -952,6 +1033,13 @@
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.updatePosition(
+ mClockPositionResult.clockX,
+ mClockPositionResult.clockY
+ - mKeyguardUserSwitcherController.getUserIconHeight(),
+ animateClock);
+ }
updateNotificationTranslucency();
updateClock();
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1092,6 +1180,9 @@
private void updateClock() {
mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+ }
}
public void animateToFullShade(long delay) {
@@ -1773,8 +1864,9 @@
mBarState != KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
- if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+ if (mKeyguardUserSwitcherController != null && mQsExpanded
+ && !mStackScrollerOverscrolling) {
+ mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
}
if (mQs == null) return;
mQs.setExpanded(mQsExpanded);
@@ -2608,10 +2700,6 @@
}
}
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
public void onScreenTurningOn() {
mKeyguardStatusViewController.dozeTimeTick();
}
@@ -3078,6 +3166,13 @@
true /* keyguardFadingAway */,
false /* goingToFullShade */,
mBarState);
+ if (mKeyguardUserSwitcherController != null) {
+ mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ false /* goingToFullShade */,
+ mBarState);
+ }
}
/**
@@ -3320,6 +3415,44 @@
return mNotificationStackScrollLayoutController;
}
+ /**
+ * Close the keyguard user switcher if it is open and capable of closing.
+ *
+ * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
+ * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+ *
+ * @return true if the keyguard user switcher was open, and is now closed
+ */
+ public boolean closeUserSwitcherIfOpen() {
+ if (mKeyguardUserSwitcherController != null) {
+ return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
+ true /* animate */);
+ }
+ return false;
+ }
+
+ private void updateUserSwitcherVisibility(boolean open) {
+ if (open) {
+ animateKeyguardStatusBarOut();
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ true /* goingToFullShade */,
+ mBarState);
+ setKeyguardBottomAreaVisibility(mBarState, true);
+ mNotificationContainerParent.setVisibility(View.GONE);
+ } else {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ StatusBarState.KEYGUARD,
+ false,
+ false,
+ StatusBarState.SHADE_LOCKED);
+ setKeyguardBottomAreaVisibility(mBarState, false);
+ mNotificationContainerParent.setVisibility(View.VISIBLE);
+ }
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3620,6 +3753,7 @@
private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
@Override
public void onThemeChanged() {
+ if (DEBUG) Log.d(TAG, "onThemeChanged");
final int themeResId = mView.getContext().getThemeResId();
if (mThemeResId == themeResId) {
return;
@@ -3631,11 +3765,15 @@
@Override
public void onOverlayChanged() {
+ if (DEBUG) Log.d(TAG, "onOverlayChanged");
reInflateViews();
}
@Override
- public void onUiModeChanged() {}
+ public void onDensityOrFontScaleChanged() {
+ if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
+ reInflateViews();
+ }
}
private class StatusBarStateListener implements StateListener {
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 b367406..e394ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,8 +22,6 @@
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewStub;
-import android.view.ViewStub.OnInflateListener;
import android.view.WindowInsets;
import android.widget.FrameLayout;
@@ -44,14 +42,11 @@
* The container with notification stack scroller and quick settings inside.
*/
public class NotificationsQuickSettingsContainer extends ConstraintLayout
- implements OnInflateListener, FragmentListener,
- AboveShelfObserver.HasViewAboveShelfChangedListener {
+ implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
private FrameLayout mQsFrame;
- private View mUserSwitcher;
private NotificationStackScrollLayout mStackScroller;
private View mKeyguardStatusBar;
- private boolean mInflated;
private boolean mQsExpanded;
private boolean mCustomizerAnimating;
@@ -73,9 +68,6 @@
mStackScroller = findViewById(R.id.notification_stack_scroller);
mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
- ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
- userSwitcher.setOnInflateListener(this);
- mUserSwitcher = userSwitcher;
}
@Override
@@ -119,10 +111,6 @@
// touches first but the panel gets drawn above.
mDrawingOrderedChildren.clear();
mLayoutDrawingOrder.clear();
- if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) {
- mDrawingOrderedChildren.add(mUserSwitcher);
- mLayoutDrawingOrder.add(mUserSwitcher);
- }
if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
mDrawingOrderedChildren.add(mKeyguardStatusBar);
mLayoutDrawingOrder.add(mKeyguardStatusBar);
@@ -158,14 +146,6 @@
}
@Override
- public void onInflate(ViewStub stub, View inflated) {
- if (stub == mUserSwitcher) {
- mUserSwitcher = inflated;
- mInflated = true;
- }
- }
-
- @Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
QS container = (QS) fragment;
container.setContainer(this);
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 1e19bee..e63902f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -35,6 +35,7 @@
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -89,7 +90,6 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
@@ -180,7 +180,6 @@
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -226,7 +225,6 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -442,7 +440,6 @@
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSlider.Factory mBrightnessSliderFactory;
- private final FeatureFlags mFeatureFlags;
private final List<ExpansionChangedListener> mExpansionChangedListeners;
@@ -623,7 +620,6 @@
}
};
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
private final UserSwitcherController mUserSwitcherController;
private final NetworkController mNetworkController;
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -766,8 +762,7 @@
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory,
- FeatureFlags featureFlags) {
+ BrightnessSlider.Factory brightnessSliderFactory) {
super(context);
mNotificationsController = notificationsController;
mLightBarController = lightBarController;
@@ -845,7 +840,6 @@
mDemoModeController = demoModeController;
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
- mFeatureFlags = featureFlags;
mExpansionChangedListeners = new ArrayList<>();
@@ -1187,11 +1181,9 @@
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
- if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) {
+ if (getEnableLightReveal()) {
mLightRevealScrim.setVisibility(View.VISIBLE);
mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
- } else {
- mLightRevealScrim.setVisibility(View.GONE);
}
mNotificationPanelViewController.initDependencies(
@@ -1212,9 +1204,6 @@
});
mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
- if (UserManager.get(mContext).isUserSwitcherEnabled()) {
- createUserSwitcher();
- }
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
@@ -1441,9 +1430,6 @@
// TODO: Bring these out of StatusBar.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
mUserSwitcherController.onDensityOrFontScaleChanged();
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
- }
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
mHeadsUpManager.onDensityOrFontScaleChanged();
}
@@ -1477,13 +1463,6 @@
}
}
- protected void createUserSwitcher() {
- mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
- mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
- mNotificationPanelViewController);
- }
-
private void inflateStatusBarWindow() {
mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
@@ -3266,7 +3245,7 @@
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
- } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){
+ } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
updatePanelExpansionForKeyguard();
@@ -3565,15 +3544,15 @@
}
return true;
}
+ if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
+ return true;
+ }
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
}
return true;
}
- if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) {
- return true;
- }
return false;
}
@@ -3622,20 +3601,8 @@
updateTheme();
mNavigationBarController.touchAutoDim(mDisplayId);
Trace.beginSection("StatusBar#updateKeyguardState");
- if (mState == StatusBarState.KEYGUARD) {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.setKeyguard(true,
- mStatusBarStateController.fromShadeLocked());
- }
- if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables();
- } else {
- if (mKeyguardUserSwitcher != null) {
- mKeyguardUserSwitcher.setKeyguard(false,
- mStatusBarStateController.goingToFullShade() ||
- mState == StatusBarState.SHADE_LOCKED ||
- mStatusBarStateController.fromShadeLocked());
- }
-
+ if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+ mStatusBarView.removePendingHideExpandedRunnables();
}
updateDozingState();
checkBarModes();
@@ -3647,7 +3614,7 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
- if (mFeatureFlags.useNewLockscreenAnimations()) {
+ if (getEnableLightReveal()) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 00acd7b..8620376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -42,8 +42,8 @@
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.List;
@@ -66,7 +66,7 @@
/**
* Display the no calling & SMS icons.
*/
- void setNoCallingIcons(String slot, List<NoCallingIconState> states);
+ void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states);
public void setIconVisibility(String slot, boolean b);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 5e8d590..f0c8527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -34,8 +34,8 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -206,6 +206,7 @@
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
+
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -218,23 +219,25 @@
}
/**
- * Accept a list of NoCallingIconStates, and show them in the same slot
+ * Accept a list of CallIndicatorIconStates, and show them in the same slot
* @param slot StatusBar slot
* @param states All of the no Calling & SMS icon states
*/
@Override
- public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+ public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
Slot noCallingSlot = getSlot(slot);
int slotIndex = getSlotIndex(slot);
-
- for (NoCallingIconState state : states) {
+ for (CallIndicatorIconState state : states) {
StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
if (holder == null) {
- holder = StatusBarIconHolder.fromNoCallingState(mContext, state);
- holder.setVisible(state.visible);
+ holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
setIcon(slotIndex, holder);
} else {
- holder.setVisible(state.visible);
+ int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+ String contentDescription = state.isNoCalling
+ ? state.noCallingDescription : state.callStrengthDescription;
+ holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
+ Icon.createWithResource(mContext, resId), 0, 0, contentDescription));
setIcon(slotIndex, holder);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 36a0e63..a1a2d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -22,8 +22,8 @@
import android.os.UserHandle;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
/**
@@ -72,14 +72,18 @@
}
/**
- * Creates a new StatusBarIconHolder from a NoCallingIconState.
+ * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
- public static StatusBarIconHolder fromNoCallingState(
- Context context, NoCallingIconState state) {
+ public static StatusBarIconHolder fromCallIndicatorState(
+ Context context, CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
+ int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+ String contentDescription = state.isNoCalling
+ ? state.noCallingDescription : state.callStrengthDescription;
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, state.resId), 0, 0, null);
+ Icon.createWithResource(context, resId), 0, 0, contentDescription);
holder.mTag = state.subId;
+ holder.setVisible(true);
return holder;
}
@@ -92,6 +96,10 @@
return mIcon;
}
+ public void setIcon(StatusBarIcon icon) {
+ mIcon = icon;
+ }
+
@Nullable
public WifiIconState getWifiState() {
return mWifiState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index f6165f6..7bc1bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -22,6 +22,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -39,7 +40,7 @@
public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
SecurityController.SecurityControllerCallback, Tunable {
private static final String TAG = "StatusBarSignalPolicy";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.INFO);
private final String mSlotAirplane;
private final String mSlotMobile;
@@ -67,7 +68,8 @@
private boolean mWifiVisible = false;
private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
- private ArrayList<NoCallingIconState> mNoCallingStates = new ArrayList<NoCallingIconState>();
+ private ArrayList<CallIndicatorIconState> mCallIndicatorStates =
+ new ArrayList<CallIndicatorIconState>();
private WifiIconState mWifiIconState = new WifiIconState();
public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
@@ -201,19 +203,25 @@
}
@Override
- public void setNoCallingStatus(boolean noCalling, int subId) {
+ public void setCallIndicator(IconState statusIcon, int subId) {
if (DEBUG) {
- Log.d(TAG, "setNoCallingStatus: "
- + "noCalling = " + noCalling + ","
+ Log.d(TAG, "setCallIndicator: "
+ + "statusIcon = " + statusIcon + ","
+ "subId = " + subId);
}
- NoCallingIconState state = getNoCallingState(subId);
+ CallIndicatorIconState state = getNoCallingState(subId);
if (state == null) {
return;
}
- state.visible = noCalling;
- mIconController.setNoCallingIcons(
- mSlotNoCalling, NoCallingIconState.copyStates(mNoCallingStates));
+ if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+ state.isNoCalling = statusIcon.visible;
+ state.noCallingDescription = statusIcon.contentDescription;
+ } else {
+ state.callStrengthResId = statusIcon.icon;
+ state.callStrengthDescription = statusIcon.contentDescription;
+ }
+ mIconController.setCallIndicatorIcons(
+ mSlotNoCalling, CallIndicatorIconState.copyStates(mCallIndicatorStates));
}
@Override
@@ -273,8 +281,8 @@
}
}
- private NoCallingIconState getNoCallingState(int subId) {
- for (NoCallingIconState state : mNoCallingStates) {
+ private CallIndicatorIconState getNoCallingState(int subId) {
+ for (CallIndicatorIconState state : mCallIndicatorStates) {
if (state.subId == subId) {
return state;
}
@@ -315,23 +323,25 @@
}
mIconController.removeAllIconsForSlot(mSlotMobile);
+ mIconController.removeAllIconsForSlot(mSlotNoCalling);
mMobileStates.clear();
- List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
- noCallingStates.addAll(mNoCallingStates);
- mNoCallingStates.clear();
+ List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
+ noCallingStates.addAll(mCallIndicatorStates);
+ mCallIndicatorStates.clear();
final int n = subs.size();
for (int i = 0; i < n; i++) {
mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
boolean isNewSub = true;
- for (NoCallingIconState state : noCallingStates) {
+ for (CallIndicatorIconState state : noCallingStates) {
if (state.subId == subs.get(i).getSubscriptionId()) {
- mNoCallingStates.add(state);
+ mCallIndicatorStates.add(state);
isNewSub = false;
break;
}
}
if (isNewSub) {
- mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+ mCallIndicatorStates.add(
+ new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
}
}
}
@@ -425,14 +435,18 @@
/**
* Stores the StatusBar state for no Calling & SMS.
*/
- public static class NoCallingIconState {
- public boolean visible;
- public int resId;
+ public static class CallIndicatorIconState {
+ public boolean isNoCalling;
+ public int noCallingResId;
+ public int callStrengthResId;
public int subId;
+ public String noCallingDescription;
+ public String callStrengthDescription;
- private NoCallingIconState(int subId) {
+ private CallIndicatorIconState(int subId) {
this.subId = subId;
- this.resId = R.drawable.ic_qs_no_calling_sms;
+ this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+ this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
}
@Override
@@ -441,27 +455,36 @@
if (o == null || getClass() != o.getClass()) {
return false;
}
- NoCallingIconState that = (NoCallingIconState) o;
- return visible == that.visible
- && resId == that.resId
- && subId == that.subId;
+ CallIndicatorIconState that = (CallIndicatorIconState) o;
+ return isNoCalling == that.isNoCalling
+ && noCallingResId == that.noCallingResId
+ && callStrengthResId == that.callStrengthResId
+ && subId == that.subId
+ && noCallingDescription == that.noCallingDescription
+ && callStrengthDescription == that.callStrengthDescription;
+
}
@Override
public int hashCode() {
- return Objects.hash(visible, resId, subId);
+ return Objects.hash(isNoCalling, noCallingResId,
+ callStrengthResId, subId, noCallingDescription, callStrengthDescription);
}
- private void copyTo(NoCallingIconState other) {
- other.visible = visible;
- other.resId = resId;
+ private void copyTo(CallIndicatorIconState other) {
+ other.isNoCalling = isNoCalling;
+ other.noCallingResId = noCallingResId;
+ other.callStrengthResId = callStrengthResId;
other.subId = subId;
+ other.noCallingDescription = noCallingDescription;
+ other.callStrengthDescription = callStrengthDescription;
}
- private static List<NoCallingIconState> copyStates(List<NoCallingIconState> inStates) {
- ArrayList<NoCallingIconState> outStates = new ArrayList<>();
- for (NoCallingIconState state : inStates) {
- NoCallingIconState copy = new NoCallingIconState(state.subId);
+ private static List<CallIndicatorIconState> copyStates(
+ List<CallIndicatorIconState> inStates) {
+ ArrayList<CallIndicatorIconState> outStates = new ArrayList<>();
+ for (CallIndicatorIconState state : inStates) {
+ CallIndicatorIconState copy = new CallIndicatorIconState(state.subId);
state.copyTo(copy);
outStates.add(copy);
}
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 b572c57..9e9533d 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
@@ -47,7 +47,6 @@
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -201,8 +200,7 @@
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory,
- FeatureFlags featureFlags) {
+ BrightnessSlider.Factory brightnessSliderFactory) {
return new StatusBar(
context,
notificationsController,
@@ -281,7 +279,6 @@
notificationShadeDepthController,
statusBarTouchableRegionManager,
notificationIconAreaController,
- brightnessSliderFactory,
- featureFlags);
+ brightnessSliderFactory);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index ccaa1f4..5ff8970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -36,6 +36,7 @@
* the current or specified Looper.
*/
public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+ private static final String TAG = "CallbackHandler";
private static final int MSG_EMERGENCE_CHANGED = 0;
private static final int MSG_SUBS_CHANGED = 1;
private static final int MSG_NO_SIM_VISIBLE_CHANGED = 2;
@@ -198,17 +199,17 @@
}
@Override
- public void setNoCallingStatus(boolean noCalling, int subId) {
+ public void setCallIndicator(IconState statusIcon, int subId) {
String log = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setNoCallingStatus: ")
- .append("noCalling=").append(noCalling).append(",")
+ .append("setCallIndicator: ")
+ .append("statusIcon=").append(statusIcon).append(",")
.append("subId=").append(subId)
.toString();
recordLastCallback(log);
post(() -> {
for (SignalCallback signalCluster : mSignalCallbacks) {
- signalCluster.setNoCallingStatus(noCalling, subId);
+ signalCluster.setCallIndicator(statusIcon, subId);
}
});
}
@@ -226,24 +227,11 @@
@Override
public void setNoSims(boolean show, boolean simDetected) {
- String log = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setNoSims: ")
- .append("show=").append(show).append(",")
- .append("simDetected=").append(simDetected)
- .toString();
- recordLastCallback(log);
obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
}
@Override
public void setMobileDataEnabled(boolean enabled) {
- String log = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setMobileDataEnabled: ")
- .append("enabled=").append(enabled)
- .toString();
- recordLastCallback(log);
obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
}
@@ -283,7 +271,8 @@
}
protected void recordLastCallback(String callback) {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+ mHistory[mHistoryIndex] = callback;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
/**
@@ -293,7 +282,9 @@
pw.println(" - CallbackHandler -----");
int size = 0;
for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mHistory[i] != null) size++;
+ if (mHistory[i] != null) {
+ size++;
+ }
}
// Print out the previous states in ordered number.
for (int i = mHistoryIndex + HISTORY_SIZE - 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 07433e1..0649478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -17,8 +17,15 @@
package com.android.systemui.statusbar.policy;
import android.content.Context;
+import android.graphics.Color;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import androidx.core.graphics.ColorUtils;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
@@ -27,6 +34,14 @@
*/
public class KeyguardUserDetailItemView extends UserDetailItemView {
+ private static final String TAG = "KeyguardUserDetailItemView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final int ANIMATION_DURATION_FADE_NAME = 240;
+
+ private float mDarkAmount;
+ private int mTextColor;
+
public KeyguardUserDetailItemView(Context context) {
this(context, null);
}
@@ -48,4 +63,89 @@
protected int getFontSizeDimen() {
return R.dimen.kg_user_switcher_text_size;
}
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTextColor = mName.getCurrentTextColor();
+ updateDark();
+ }
+
+ /**
+ * Update visibility of this view.
+ *
+ * @param showItem If true, this item is visible on the screen to the user. Generally this
+ * means that the item would be clickable. If false, item visibility will be
+ * set to GONE and hidden entirely.
+ * @param showTextName Whether or not the name should be shown next to the icon. If false,
+ * only the icon is shown.
+ * @param animate Whether the transition should be animated. Note, this only applies to
+ * animating the text name. The item itself will not animate (i.e. fade in/out).
+ * Instead, we delegate that to the parent view.
+ */
+ void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
+ showItem, showTextName, animate));
+ }
+
+ getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
+
+ if (showItem) {
+ if (showTextName) {
+ mName.setVisibility(View.VISIBLE);
+ if (animate) {
+ mName.setAlpha(0f);
+ mName.animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FADE_NAME)
+ .setInterpolator(Interpolators.ALPHA_IN);
+ } else {
+ mName.setAlpha(1f);
+ }
+ } else {
+ if (animate) {
+ mName.setVisibility(View.VISIBLE);
+ mName.setAlpha(1f);
+ mName.animate()
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION_FADE_NAME)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(() -> {
+ mName.setVisibility(View.GONE);
+ mName.setAlpha(1f);
+ });
+ } else {
+ mName.setVisibility(View.GONE);
+ mName.setAlpha(1f);
+ }
+ }
+ setVisibility(View.VISIBLE);
+ setAlpha(1f);
+ } else {
+ // If item isn't shown, don't animate. The parent class will animate the view instead
+ setVisibility(View.GONE);
+ setAlpha(1f);
+ mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
+ mName.setAlpha(1f);
+ }
+ }
+
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ public void setDarkAmount(float darkAmount) {
+ if (mDarkAmount == darkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ updateDark();
+ }
+
+ private void updateDark() {
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mName.setTextColor(blendedTextColor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
deleted file mode 100644
index 90f5577..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.FrameLayout;
-
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-
-import java.util.ArrayList;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-public class KeyguardUserSwitcher {
-
- private static final String TAG = "KeyguardUserSwitcher";
- private static final boolean ALWAYS_ON = false;
-
- private final Container mUserSwitcherContainer;
- private final KeyguardStatusBarView mStatusBarView;
- private final KeyguardUserAdapter mAdapter;
- private final AppearAnimationUtils mAppearAnimationUtils;
- private final KeyguardUserSwitcherScrim mBackground;
-
- private ViewGroup mUserSwitcher;
- private ObjectAnimator mBgAnimator;
- private UserSwitcherController mUserSwitcherController;
- private boolean mAnimating;
-
- public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView,
- NotificationPanelViewController panelViewController) {
- boolean keyguardUserSwitcherEnabled =
- context.getResources().getBoolean(
- com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
- UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
- if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
- mUserSwitcherContainer = (Container) userSwitcher.inflate();
- mBackground = new KeyguardUserSwitcherScrim(context);
- reinflateViews();
- mStatusBarView = statusBarView;
- mStatusBarView.setKeyguardUserSwitcher(this);
- panelViewController.setKeyguardUserSwitcher(this);
- mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
- mAdapter.registerDataSetObserver(mDataSetObserver);
- mUserSwitcherController = userSwitcherController;
- mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
- Interpolators.FAST_OUT_SLOW_IN);
- mUserSwitcherContainer.setKeyguardUserSwitcher(this);
- } else {
- mUserSwitcherContainer = null;
- mStatusBarView = null;
- mAdapter = null;
- mAppearAnimationUtils = null;
- mBackground = null;
- }
- }
-
- private void reinflateViews() {
- if (mUserSwitcher != null) {
- mUserSwitcher.setBackground(null);
- mUserSwitcher.removeOnLayoutChangeListener(mBackground);
- }
- mUserSwitcherContainer.removeAllViews();
-
- LayoutInflater.from(mUserSwitcherContainer.getContext())
- .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer);
-
- mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById(
- R.id.keyguard_user_switcher_inner);
- mUserSwitcher.addOnLayoutChangeListener(mBackground);
- mUserSwitcher.setBackground(mBackground);
- }
-
- public void setKeyguard(boolean keyguard, boolean animate) {
- if (mUserSwitcher != null) {
- if (keyguard && shouldExpandByDefault()) {
- show(animate);
- } else {
- hide(animate);
- }
- }
- }
-
- /**
- * @return true if the user switcher should be expanded by default on the lock screen.
- * @see android.os.UserManager#isUserSwitcherEnabled()
- */
- private boolean shouldExpandByDefault() {
- return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher();
- }
-
- public void show(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
- cancelAnimations();
- mAdapter.refresh();
- mUserSwitcherContainer.setVisibility(View.VISIBLE);
- mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
- if (animate) {
- startAppearAnimation();
- }
- }
- }
-
- private boolean hide(boolean animate) {
- if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
- cancelAnimations();
- if (animate) {
- startDisappearAnimation();
- } else {
- mUserSwitcherContainer.setVisibility(View.GONE);
- }
- mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
- return true;
- }
- return false;
- }
-
- private void cancelAnimations() {
- int count = mUserSwitcher.getChildCount();
- for (int i = 0; i < count; i++) {
- mUserSwitcher.getChildAt(i).animate().cancel();
- }
- if (mBgAnimator != null) {
- mBgAnimator.cancel();
- }
- mUserSwitcher.animate().cancel();
- mAnimating = false;
- }
-
- private void startAppearAnimation() {
- int count = mUserSwitcher.getChildCount();
- View[] objects = new View[count];
- for (int i = 0; i < count; i++) {
- objects[i] = mUserSwitcher.getChildAt(i);
- }
- mUserSwitcher.setClipChildren(false);
- mUserSwitcher.setClipToPadding(false);
- mAppearAnimationUtils.startAnimation(objects, new Runnable() {
- @Override
- public void run() {
- mUserSwitcher.setClipChildren(true);
- mUserSwitcher.setClipToPadding(true);
- }
- });
- mAnimating = true;
- mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
- mBgAnimator.setDuration(400);
- mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mBgAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBgAnimator = null;
- mAnimating = false;
- }
- });
- mBgAnimator.start();
- }
-
- private void startDisappearAnimation() {
- mAnimating = true;
- mUserSwitcher.animate()
- .alpha(0f)
- .setDuration(300)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mUserSwitcherContainer.setVisibility(View.GONE);
- mUserSwitcher.setAlpha(1f);
- mAnimating = false;
- }
- });
- }
-
- private void refresh() {
- final int childCount = mUserSwitcher.getChildCount();
- final int adapterCount = mAdapter.getCount();
- final int N = Math.max(childCount, adapterCount);
- for (int i = 0; i < N; i++) {
- if (i < adapterCount) {
- View oldView = null;
- if (i < childCount) {
- oldView = mUserSwitcher.getChildAt(i);
- }
- View newView = mAdapter.getView(i, oldView, mUserSwitcher);
- if (oldView == null) {
- // We ran out of existing views. Add it at the end.
- mUserSwitcher.addView(newView);
- } else if (oldView != newView) {
- // We couldn't rebind the view. Replace it.
- mUserSwitcher.removeViewAt(i);
- mUserSwitcher.addView(newView, i);
- }
- } else {
- int lastIndex = mUserSwitcher.getChildCount() - 1;
- mUserSwitcher.removeViewAt(lastIndex);
- }
- }
- }
-
- public boolean hideIfNotSimple(boolean animate) {
- if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
- return hide(animate);
- }
- return false;
- }
-
- boolean isAnimating() {
- return mAnimating;
- }
-
- public final DataSetObserver mDataSetObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- refresh();
- }
- };
-
- public void onDensityOrFontScaleChanged() {
- if (mUserSwitcherContainer != null) {
- reinflateViews();
- refresh();
- }
- }
-
- static class KeyguardUserAdapter extends
- UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
-
- private Context mContext;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private View mCurrentUserView;
- // List of users where the first entry is always the current user
- private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
-
- KeyguardUserAdapter(Context context, UserSwitcherController controller,
- KeyguardUserSwitcher kgu) {
- super(controller);
- mContext = context;
- mKeyguardUserSwitcher = kgu;
- }
-
- @Override
- public void notifyDataSetChanged() {
- refreshUserOrder();
- super.notifyDataSetChanged();
- }
-
- void refreshUserOrder() {
- ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
- mUsersOrdered = new ArrayList<>(users.size());
- for (int i = 0; i < users.size(); i++) {
- UserSwitcherController.UserRecord record = users.get(i);
- if (record.isCurrent) {
- mUsersOrdered.add(0, record);
- } else {
- mUsersOrdered.add(record);
- }
- }
- }
-
- @Override
- protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
- return mUsersOrdered;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- UserSwitcherController.UserRecord item = getItem(position);
- return createUserDetailItemView(convertView, parent, item);
- }
-
- KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
- if (!(convertView instanceof KeyguardUserDetailItemView)
- || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
- convertView = LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_user_switcher_item, parent, false);
- }
- return (KeyguardUserDetailItemView) convertView;
- }
-
- UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
- UserSwitcherController.UserRecord item) {
- KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
- if (!item.isCurrent || item.isGuest) {
- v.setOnClickListener(this);
- } else {
- v.setOnClickListener(null);
- v.setClickable(false);
- }
-
- String name = getName(mContext, item);
- if (item.picture == null) {
- v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
- } else {
- int avatarSize =
- (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
- Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
- drawable.setColorFilter(
- item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
- v.bind(name, drawable, item.info.id);
- }
- v.setActivated(item.isCurrent);
- v.setDisabledByAdmin(item.isDisabledByAdmin);
- v.setEnabled(item.isSwitchToEnabled);
- v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
-
- if (item.isCurrent) {
- mCurrentUserView = v;
- }
- v.setTag(item);
- return v;
- }
-
- private static Drawable getDrawable(Context context,
- UserSwitcherController.UserRecord item) {
- Drawable drawable = getIconDrawable(context, item);
- int iconColorRes;
- if (item.isCurrent) {
- iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color;
- } else if (!item.isSwitchToEnabled) {
- iconColorRes = R.color.GM2_grey_600;
- } else {
- iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
- }
- drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme()));
-
- if (item.isCurrent) {
- Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected);
- drawable = new LayerDrawable(new Drawable[]{bg, drawable});
- }
-
- return drawable;
- }
-
- @Override
- public void onClick(View v) {
- UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
- if (user.isCurrent && !user.isGuest) {
- // Close the switcher if tapping the current user. Guest is excluded because
- // tapping the guest user while it's current clears the session.
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
- } else if (user.isSwitchToEnabled) {
- if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
- if (mCurrentUserView != null) {
- mCurrentUserView.setActivated(false);
- }
- v.setActivated(true);
- }
- onUserListItemClicked(user);
- }
- }
- }
-
- public static class Container extends FrameLayout {
-
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
-
- public Container(Context context, AttributeSet attrs) {
- super(context, attrs);
- setClipChildren(false);
- }
-
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- // Hide switcher if it didn't handle the touch event (and let the event go through).
- if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
- }
- return false;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
new file mode 100644
index 0000000..b76e451
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -0,0 +1,639 @@
+/*
+ * 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 static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+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.util.ViewController;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
+
+ private static final String TAG = "KeyguardUserSwitcherController";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final AnimationProperties ANIMATION_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ private final Context mContext;
+ private final UserSwitcherController mUserSwitcherController;
+ private final ScreenLifecycle mScreenLifecycle;
+ private final KeyguardUserAdapter mAdapter;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
+ private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+
+ // Child views of KeyguardUserSwitcherView
+ private KeyguardUserSwitcherListView mListView;
+ private LinearLayout mEndGuestButton;
+
+ // State info for the user switcher
+ private boolean mUserSwitcherOpen;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private boolean mCurrentUserIsGuest;
+ private int mBarState;
+ private float mDarkAmount;
+
+ private final KeyguardUpdateMonitorCallback mInfoCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+ // Any time the keyguard is hidden, try to close the user switcher menu to
+ // restore keyguard to the default state
+ if (!showing) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ }
+
+ @Override
+ public void onUserSwitching(int userId) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ };
+
+ private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+ @Override
+ public void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ };
+
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+ boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+ boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+ int oldState = mBarState;
+ mBarState = newState;
+
+ if (mStatusBarStateController.goingToFullShade()
+ || mKeyguardStateController.isKeyguardFadingAway()) {
+ closeSwitcherIfOpenAndNotSimple(true);
+ }
+
+ setKeyguardUserSwitcherVisibility(
+ newState,
+ keyguardFadingAway,
+ goingToFullShade,
+ oldState);
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linearAmount, float amount) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+ linearAmount, amount));
+ }
+ setDarkAmount(amount);
+ }
+ };
+
+ @Inject
+ public KeyguardUserSwitcherController(
+ KeyguardUserSwitcherView keyguardUserSwitcherView,
+ Context context,
+ @Main Resources resources,
+ LayoutInflater layoutInflater,
+ ScreenLifecycle screenLifecycle,
+ UserSwitcherController userSwitcherController,
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DozeParameters dozeParameters) {
+ super(keyguardUserSwitcherView);
+ if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
+ mContext = context;
+ mScreenLifecycle = screenLifecycle;
+ mUserSwitcherController = userSwitcherController;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
+ mUserSwitcherController, this);
+ mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+ keyguardStateController, dozeParameters);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
+
+ if (DEBUG) Log.d(TAG, "onInit");
+
+ mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
+ mEndGuestButton = mView.findViewById(R.id.end_guest_button);
+
+ mEndGuestButton.setOnClickListener(v -> {
+ mUserSwitcherController.showExitGuestDialog(mCurrentUserId);
+ });
+
+ mView.setOnTouchListener((v, event) -> {
+ if (!isListAnimating()) {
+ // Hide switcher if it didn't handle the touch event (and block the event from
+ // going through).
+ return closeSwitcherIfOpenAndNotSimple(true);
+ }
+ return false;
+ });
+ }
+
+ @Override
+ protected void onViewAttached() {
+ if (DEBUG) Log.d(TAG, "onViewAttached");
+ mAdapter.registerDataSetObserver(mDataSetObserver);
+ mDataSetObserver.onChanged();
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mScreenLifecycle.addObserver(mScreenObserver);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ if (DEBUG) Log.d(TAG, "onViewDetached");
+
+ // Detaching the view will always close the switcher
+ closeSwitcherIfOpenAndNotSimple(false);
+
+ mAdapter.unregisterDataSetObserver(mDataSetObserver);
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mScreenLifecycle.removeObserver(mScreenObserver);
+ }
+
+ /**
+ * See:
+ *
+ * <ul>
+ * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
+ * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
+ * </ul>
+ *
+ * @return true if the user switcher should be open by default on the lock screen.
+ * @see android.os.UserManager#isUserSwitcherEnabled()
+ */
+ public boolean isSimpleUserSwitcher() {
+ return mUserSwitcherController.isSimpleUserSwitcher();
+ }
+
+ /**
+ * @param animate if the transition should be animated
+ * @return true if the switcher state changed
+ */
+ public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
+ if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
+ setUserSwitcherOpened(false /* open */, animate);
+ return true;
+ }
+ return false;
+ }
+
+ public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ refreshUserList();
+ }
+ };
+
+ void refreshUserList() {
+ final int childCount = mListView.getChildCount();
+ final int adapterCount = mAdapter.getCount();
+ final int count = Math.max(childCount, adapterCount);
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
+ adapterCount));
+ }
+
+ boolean foundCurrentUser = false;
+ for (int i = 0; i < count; i++) {
+ if (i < adapterCount) {
+ View oldView = null;
+ if (i < childCount) {
+ oldView = mListView.getChildAt(i);
+ }
+ KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
+ mAdapter.getView(i, oldView, mListView);
+ UserSwitcherController.UserRecord userTag =
+ (UserSwitcherController.UserRecord) newView.getTag();
+ if (userTag.isCurrent) {
+ if (i != 0) {
+ Log.w(TAG, "Current user is not the first view in the list");
+ }
+ foundCurrentUser = true;
+ mCurrentUserId = userTag.info.id;
+ mCurrentUserIsGuest = userTag.isGuest;
+ // Current user is always visible
+ newView.updateVisibilities(true /* showItem */,
+ mUserSwitcherOpen /* showTextName */, false /* animate */);
+ } else {
+ // Views for non-current users are always expanded (e.g. they should the name
+ // next to the user icon). However, they could be hidden entirely if the list
+ // is closed.
+ newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
+ true /* showTextName */, false /* animate */);
+ }
+ newView.setDarkAmount(mDarkAmount);
+ if (oldView == null) {
+ // We ran out of existing views. Add it at the end.
+ mListView.addView(newView);
+ } else if (oldView != newView) {
+ // We couldn't rebind the view. Replace it.
+ mListView.replaceView(newView, i);
+ }
+ } else {
+ mListView.removeLastView();
+ }
+ }
+ if (!foundCurrentUser) {
+ Log.w(TAG, "Current user is not listed");
+ mCurrentUserId = UserHandle.USER_NULL;
+ mCurrentUserIsGuest = false;
+ }
+ }
+
+ /**
+ * Get the height of the keyguard user switcher view when closed.
+ */
+ public int getUserIconHeight() {
+ View firstChild = mListView.getChildAt(0);
+ return firstChild == null ? 0 : firstChild.getHeight();
+ }
+
+ /**
+ * Set the visibility of the keyguard user switcher view based on some new state.
+ */
+ public void setKeyguardUserSwitcherVisibility(
+ int statusBarState,
+ boolean keyguardFadingAway,
+ boolean goingToFullShade,
+ int oldStatusBarState) {
+ mKeyguardVisibilityHelper.setViewVisibility(
+ statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+ }
+
+ /**
+ * Update position of the view with an optional animation
+ */
+ public void updatePosition(int x, int y, boolean animate) {
+ PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
+ animate);
+ PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+ ANIMATION_PROPERTIES, animate);
+ }
+
+ /**
+ * Set keyguard user switcher view alpha.
+ */
+ public void setAlpha(float alpha) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ mView.setAlpha(alpha);
+ }
+ }
+
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ private void setDarkAmount(float darkAmount) {
+ boolean isAwake = darkAmount != 0;
+ if (darkAmount == mDarkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ mListView.setDarkAmount(darkAmount);
+ mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+ if (!isAwake) {
+ closeSwitcherIfOpenAndNotSimple(false);
+ }
+ }
+
+ private boolean isListAnimating() {
+ return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
+ }
+
+ /**
+ * Remove the callback if it exists.
+ */
+ public void removeCallback() {
+ if (DEBUG) Log.d(TAG, "removeCallback");
+ mKeyguardUserSwitcherCallback = null;
+ }
+
+ /**
+ * Register to receive notifications about keyguard user switcher state
+ * (see {@link KeyguardUserSwitcherListener}.
+ *
+ * Only one callback can be used at a time.
+ *
+ * @param callback The callback to register
+ */
+ public void setCallback(KeyguardUserSwitcherListener callback) {
+ if (DEBUG) Log.d(TAG, "setCallback");
+ mKeyguardUserSwitcherCallback = new WeakReference<>(callback);
+ }
+
+ /**
+ * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}.
+ * Switcher state is updatd before animations finish.
+ *
+ * @param animate true to animate transition. The user switcher state (i.e.
+ * {@link #isUserSwitcherOpen()}) is updated before animation is finished.
+ */
+ private void setUserSwitcherOpened(boolean open, boolean animate) {
+ boolean wasOpen = mUserSwitcherOpen;
+ if (DEBUG) {
+ Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen,
+ open, animate));
+ }
+ mUserSwitcherOpen = open;
+ if (mUserSwitcherOpen != wasOpen) {
+ notifyUserSwitcherStateChanged();
+ }
+ updateVisibilities(animate);
+ }
+
+ private void updateVisibilities(boolean animate) {
+ if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
+ mEndGuestButton.animate().cancel();
+ if (mUserSwitcherOpen && mCurrentUserIsGuest) {
+ // Show the "End guest session" button
+ mEndGuestButton.setVisibility(View.VISIBLE);
+ if (animate) {
+ mEndGuestButton.setAlpha(0f);
+ mEndGuestButton.animate()
+ .alpha(1f)
+ .setDuration(360)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .withEndAction(() -> {
+ mEndGuestButton.setClickable(true);
+ });
+ } else {
+ mEndGuestButton.setClickable(true);
+ mEndGuestButton.setAlpha(1f);
+ }
+ } else {
+ // Hide the "End guest session" button. If it's already GONE, don't try to
+ // animate it or it will appear again for an instant.
+ mEndGuestButton.setClickable(false);
+ if (animate && mEndGuestButton.getVisibility() != View.GONE) {
+ mEndGuestButton.setVisibility(View.VISIBLE);
+ mEndGuestButton.setAlpha(1f);
+ mEndGuestButton.animate()
+ .alpha(0f)
+ .setDuration(360)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(() -> {
+ mEndGuestButton.setVisibility(View.GONE);
+ mEndGuestButton.setAlpha(1f);
+ });
+ } else {
+ mEndGuestButton.setVisibility(View.GONE);
+ mEndGuestButton.setAlpha(1f);
+ }
+ }
+
+ mListView.updateVisibilities(mUserSwitcherOpen, animate);
+ }
+
+ private boolean isUserSwitcherOpen() {
+ return mUserSwitcherOpen;
+ }
+
+ private void notifyUserSwitcherStateChanged() {
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b",
+ mUserSwitcherOpen));
+ }
+ if (mKeyguardUserSwitcherCallback != null) {
+ KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get();
+ if (cb != null) {
+ cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen);
+ }
+ }
+ }
+
+ /**
+ * Callback for keyguard user switcher state information
+ */
+ public interface KeyguardUserSwitcherListener {
+
+ /**
+ * Called when the keyguard enters or leaves user switcher mode. This will be called
+ * before the animations are finished.
+ *
+ * @param open if true, keyguard is showing the user switcher or transitioning from/to user
+ * switcher mode.
+ */
+ void onKeyguardUserSwitcherChanged(boolean open);
+ }
+
+ static class KeyguardUserAdapter extends
+ UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final LayoutInflater mLayoutInflater;
+ private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+ private View mCurrentUserView;
+ // List of users where the first entry is always the current user
+ private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+
+ KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
+ UserSwitcherController controller,
+ KeyguardUserSwitcherController keyguardUserSwitcherController) {
+ super(controller);
+ mContext = context;
+ mResources = resources;
+ mLayoutInflater = layoutInflater;
+ mKeyguardUserSwitcherController = keyguardUserSwitcherController;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
+ // data set
+ refreshUserOrder();
+ super.notifyDataSetChanged();
+ }
+
+ void refreshUserOrder() {
+ ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+ mUsersOrdered = new ArrayList<>(users.size());
+ for (int i = 0; i < users.size(); i++) {
+ UserSwitcherController.UserRecord record = users.get(i);
+ if (record.isCurrent) {
+ mUsersOrdered.add(0, record);
+ } else {
+ mUsersOrdered.add(record);
+ }
+ }
+ }
+
+ @Override
+ protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+ return mUsersOrdered;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ UserSwitcherController.UserRecord item = getItem(position);
+ return createUserDetailItemView(convertView, parent, item);
+ }
+
+ @Override
+ public String getName(Context context, UserSwitcherController.UserRecord item) {
+ if (item.isGuest) {
+ return context.getString(com.android.settingslib.R.string.guest_nickname);
+ } else {
+ return super.getName(context, item);
+ }
+ }
+
+ KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+ if (!(convertView instanceof KeyguardUserDetailItemView)
+ || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+ convertView = mLayoutInflater.inflate(
+ R.layout.keyguard_user_switcher_item, parent, false);
+ }
+ return (KeyguardUserDetailItemView) convertView;
+ }
+
+ KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+ UserSwitcherController.UserRecord item) {
+ KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+ v.setOnClickListener(this);
+
+ String name = getName(mContext, item);
+ if (item.picture == null) {
+ v.bind(name, getDrawable(item).mutate(), item.resolveId());
+ } else {
+ int avatarSize =
+ (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+ Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
+ drawable.setColorFilter(
+ item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
+ v.bind(name, drawable, item.info.id);
+ }
+ v.setActivated(item.isCurrent);
+ v.setDisabledByAdmin(item.isDisabledByAdmin);
+ v.setEnabled(item.isSwitchToEnabled);
+ v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+
+ if (item.isCurrent) {
+ mCurrentUserView = v;
+ }
+ v.setTag(item);
+ return v;
+ }
+
+ private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+ Drawable drawable;
+ if (item.isCurrent && item.isGuest) {
+ drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+ } else {
+ drawable = getIconDrawable(mContext, item);
+ }
+
+ int iconColorRes;
+ if (item.isSwitchToEnabled) {
+ iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+ } else {
+ iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+ }
+ drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+ Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+ drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+ return drawable;
+ }
+
+ @Override
+ public void onClick(View v) {
+ UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+
+ if (mKeyguardUserSwitcherController.isListAnimating()) {
+ return;
+ }
+
+ if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
+ if (user.isCurrent) {
+ // Close the switcher if tapping the current user
+ mKeyguardUserSwitcherController.setUserSwitcherOpened(
+ false /* open */, true /* animate */);
+ } else if (user.isSwitchToEnabled) {
+ if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
+ if (mCurrentUserView != null) {
+ mCurrentUserView.setActivated(false);
+ }
+ v.setActivated(true);
+ }
+ onUserListItemClicked(user);
+ }
+ } else {
+ // If switcher is closed, tapping anywhere in the view will open it
+ mKeyguardUserSwitcherController.setUserSwitcherOpened(
+ true /* open */, true /* animate */);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
new file mode 100644
index 0000000..7c82c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -0,0 +1,168 @@
+/*
+ * 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.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.keyguard.KeyguardConstants;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Interpolators;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
+
+ private static final String TAG = "KeyguardUserSwitcherListView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private static final int ANIMATION_DURATION_OPENING = 360;
+ private static final int ANIMATION_DURATION_CLOSING = 240;
+
+ private boolean mAnimating;
+ private final AppearAnimationUtils mAppearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtils;
+
+ public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setClipChildren(false);
+ mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING,
+ -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN);
+ mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING,
+ 0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN);
+ }
+
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ void setDarkAmount(float darkAmount) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (v instanceof KeyguardUserDetailItemView) {
+ ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
+ }
+ }
+ }
+
+ boolean isAnimating() {
+ return mAnimating;
+ }
+
+ /**
+ * Update visibilities of this view and child views for when the user list is open or closed.
+ * If closed, this hides everything but the first item (which is always the current user).
+ */
+ void updateVisibilities(boolean open, boolean animate) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
+ open, animate, getChildCount()));
+ }
+
+ mAnimating = false;
+
+ int userListCount = getChildCount();
+ if (userListCount > 0) {
+ // The first child is always the current user.
+ KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt(
+ 0));
+ currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */,
+ animate);
+ currentUserView.setClickable(true);
+ currentUserView.clearAnimation();
+ }
+
+ if (userListCount <= 1) {
+ return;
+ }
+
+ if (animate) {
+ // Create an array of all the remaining users (that aren't the current user).
+ KeyguardUserDetailItemView[] otherUserViews =
+ new KeyguardUserDetailItemView[userListCount - 1];
+ for (int i = 1, n = 0; i < userListCount; i++, n++) {
+ otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i);
+
+ // Update clickable state immediately so that the menu feels more responsive
+ otherUserViews[n].setClickable(open);
+
+ // Before running the animation, ensure visibility is set correctly
+ otherUserViews[n].updateVisibilities(
+ true /* showItem */, true /* showTextName */, false /* animate */);
+ otherUserViews[n].clearAnimation();
+ }
+
+ setClipChildren(false);
+ setClipToPadding(false);
+
+ mAnimating = true;
+
+ final int nonCurrentUserCount = otherUserViews.length;
+ if (open) {
+ mAppearAnimationUtils.startAnimation(otherUserViews, () -> {
+ setClipChildren(true);
+ setClipToPadding(true);
+ mAnimating = false;
+ });
+ } else {
+ mDisappearAnimationUtils.startAnimation(otherUserViews, () -> {
+ setClipChildren(true);
+ setClipToPadding(true);
+ for (int i = 0; i < nonCurrentUserCount; i++) {
+ otherUserViews[i].updateVisibilities(
+ false /* showItem */, true /* showTextName */, false /* animate */);
+ }
+ mAnimating = false;
+ });
+ }
+ } else {
+ for (int i = 1; i < userListCount; i++) {
+ KeyguardUserDetailItemView nonCurrentUserView =
+ ((KeyguardUserDetailItemView) getChildAt(i));
+ nonCurrentUserView.clearAnimation();
+ nonCurrentUserView.updateVisibilities(
+ open /* showItem */, true /* showTextName */, false /* animate */);
+ nonCurrentUserView.setClickable(open);
+ }
+ }
+ }
+
+ /**
+ * Replaces the view at the specified position in the group.
+ *
+ * @param index the position in the group of the view to remove
+ */
+ void replaceView(KeyguardUserDetailItemView newView, int index) {
+ removeViewAt(index);
+ addView(newView, index);
+ }
+
+ /**
+ * Removes the last view in the group.
+ */
+ void removeLastView() {
+ int lastIndex = getChildCount() - 1;
+ removeViewAt(lastIndex);
+ }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
index 14d57bf..3f0e23f 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
@@ -14,6 +14,18 @@
* limitations under the License.
*/
-package android.app.timedetector;
+package com.android.systemui.statusbar.policy;
-parcelable ExternalTimeSuggestion;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherView extends FrameLayout {
+
+ public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 0fe338e..1ab7652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings.Global;
+import android.telephony.AccessNetworkConstants;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.ServiceState;
@@ -34,12 +35,18 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager.RegistrationCallback;
import android.text.Html;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.SignalIcon.MobileState;
import com.android.settingslib.Utils;
@@ -65,13 +72,19 @@
*/
public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> {
private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-
+ private static final int STATUS_HISTORY_SIZE = 64;
+ private static final int IMS_TYPE_WWAN = 1;
+ private static final int IMS_TYPE_WLAN = 2;
+ private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
private final TelephonyManager mPhone;
+ private final ImsMmTelManager mImsMmTelManager;
private final SubscriptionDefaults mDefaults;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
private final boolean mProviderModel;
+ private final Handler mReceiverHandler;
+ private int mImsType = IMS_TYPE_WWAN;
// Save entire info for logging, we only use the id.
final SubscriptionInfo mSubscriptionInfo;
// @VisibleForDemoMode
@@ -86,16 +99,21 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
private ServiceState mServiceState;
private SignalStrength mSignalStrength;
+ private int mLastLevel;
private MobileIconGroup mDefaultIcons;
private Config mConfig;
@VisibleForTesting
boolean mInflateSignalStrengths = false;
private MobileStatusTracker.Callback mCallback;
+ private RegistrationCallback mRegistrationCallback;
+ private int mLastWwanLevel;
+ private int mLastWlanLevel;
+ private int mLastWlanCrossSimLevel;
@VisibleForTesting
MobileStatusTracker mMobileStatusTracker;
- // Save the previous HISTORY_SIZE states for logging.
- private final String[] mMobileStatusHistory = new String[HISTORY_SIZE];
+ // Save the previous STATUS_HISTORY_SIZE states for logging.
+ private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE];
// Where to copy the next state into.
private int mMobileStatusHistoryIndex;
@@ -116,6 +134,7 @@
.toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
+ mReceiverHandler = new Handler(receiverLooper);
mNetworkToIconLookup = mapIconSets(mConfig);
mDefaultIcons = getDefaultIcons(mConfig);
@@ -133,6 +152,8 @@
}
};
mCallback = new MobileStatusTracker.Callback() {
+ private String mLastStatus;
+
@Override
public void onMobileStatusChanged(boolean updateTelephony,
MobileStatus mobileStatus) {
@@ -141,11 +162,15 @@
+ " updateTelephony=" + updateTelephony
+ " mobileStatus=" + mobileStatus.toString());
}
- String status = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append(mobileStatus.toString())
- .toString();
- recordLastMobileStatus(status);
+ String currentStatus = mobileStatus.toString();
+ if (!currentStatus.equals(mLastStatus)) {
+ mLastStatus = currentStatus;
+ String status = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append(currentStatus)
+ .toString();
+ recordLastMobileStatus(status);
+ }
updateMobileStatus(mobileStatus);
if (updateTelephony) {
updateTelephony();
@@ -154,6 +179,53 @@
}
}
};
+
+ mRegistrationCallback = new RegistrationCallback() {
+ @Override
+ public void onRegistered(ImsRegistrationAttributes attributes) {
+ Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
+ int imsTransportType = attributes.getTransportType();
+ int registrationAttributes = attributes.getAttributeFlags();
+ if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+ if (registrationAttributes == 0) {
+ mImsType = IMS_TYPE_WLAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+ getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (registrationAttributes
+ == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
+ mImsType = IMS_TYPE_WLAN_CROSS_SIM;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+ getCallStrengthDescription(
+ mLastWlanCrossSimLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+ }
+
+ @Override
+ public void onUnregistered(ImsReasonInfo info) {
+ Log.d(mTag, "onDeregistered: " + "info=" + info);
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ };
+ mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
info, mDefaults, mCallback);
mProviderModel = FeatureFlagUtils.isEnabled(
@@ -202,14 +274,41 @@
mContext.getContentResolver().registerContentObserver(Global.getUriFor(
Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()),
true, mObserver);
+ if (mProviderModel) {
+ mReceiverHandler.post(mTryRegisterIms);
+ }
}
+ // There is no listener to monitor whether the IMS service is ready, so we have to retry the
+ // IMS registration.
+ private final Runnable mTryRegisterIms = new Runnable() {
+ private static final int MAX_RETRY = 12;
+ private int mRetryCount;
+
+ @Override
+ public void run() {
+ try {
+ mRetryCount++;
+ mImsMmTelManager.registerImsRegistrationCallback(
+ mReceiverHandler::post, mRegistrationCallback);
+ Log.d(mTag, "registerImsRegistrationCallback succeeded");
+ } catch (RuntimeException | ImsException e) {
+ if (mRetryCount < MAX_RETRY) {
+ Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e);
+ // Wait for 5 seconds to retry
+ mReceiverHandler.postDelayed(mTryRegisterIms, 5000);
+ }
+ }
+ }
+ };
+
/**
* Stop listening for phone state changes.
*/
public void unregisterListener() {
mMobileStatusTracker.setListening(false);
mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback);
}
private void updateInflateSignalStrength() {
@@ -452,9 +551,9 @@
/**
* Extracts the CellSignalStrengthCdma from SignalStrength then returns the level
*/
- private final int getCdmaLevel() {
+ private int getCdmaLevel(SignalStrength signalStrength) {
List<CellSignalStrengthCdma> signalStrengthCdma =
- mSignalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
+ signalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
if (!signalStrengthCdma.isEmpty()) {
return signalStrengthCdma.get(0).getLevel();
}
@@ -467,6 +566,7 @@
mCurrentState.dataSim = mobileStatus.dataSim;
mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode;
mDataState = mobileStatus.dataState;
+ notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
mSignalStrength = mobileStatus.signalStrength;
mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
@@ -481,9 +581,117 @@
&& (lastVoiceState == -1
|| (lastVoiceState == ServiceState.STATE_IN_SERVICE
|| currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
- notifyNoCallingStatusChange(
- currentVoiceState != ServiceState.STATE_IN_SERVICE,
- mSubscriptionInfo.getSubscriptionId());
+ boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+ IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+ getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+
+ private int getCallStrengthIcon(int level, boolean isWifi) {
+ return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
+ : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
+ }
+
+ private String getCallStrengthDescription(int level, boolean isWifi) {
+ return isWifi
+ ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level])
+ .toString()
+ : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level])
+ .toString();
+ }
+
+ void refreshCallIndicator(SignalCallback callback) {
+ boolean isNoCalling = mServiceState != null
+ && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
+ IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+ getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+ callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+
+ switch (mImsType) {
+ case IMS_TYPE_WWAN:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ break;
+ case IMS_TYPE_WLAN:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+ getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+ break;
+ case IMS_TYPE_WLAN_CROSS_SIM:
+ statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false));
+ }
+ callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyWifiLevelChange(int level) {
+ if (!mProviderModel) {
+ return;
+ }
+ Log.d("mTag", "notifyWifiLevelChange " + mImsType);
+ mLastWlanLevel = level;
+ if (mImsType != IMS_TYPE_WLAN) {
+ return;
+ }
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(level, /* isWifi= */true),
+ getCallStrengthDescription(level, /* isWifi= */true));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyDefaultMobileLevelChange(int level) {
+ if (!mProviderModel) {
+ return;
+ }
+ Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType);
+ mLastWlanCrossSimLevel = level;
+ if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
+ return;
+ }
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(level, /* isWifi= */false),
+ getCallStrengthDescription(level, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+
+ void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
+ if (!mProviderModel) {
+ return;
+ }
+ int newLevel = getSignalLevel(signalStrength);
+ if (newLevel != mLastLevel) {
+ mLastLevel = newLevel;
+ Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType);
+ mLastWwanLevel = newLevel;
+ if (mImsType == IMS_TYPE_WWAN) {
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(newLevel, /* isWifi= */false),
+ getCallStrengthDescription(newLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ if (mCurrentState.dataSim) {
+ mNetworkController.notifyDefaultMobileLevelChange(newLevel);
+ }
+ }
+ }
+
+ int getSignalLevel(SignalStrength signalStrength) {
+ if (signalStrength == null) {
+ return 0;
+ }
+ if (!signalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+ return getCdmaLevel(signalStrength);
+ } else {
+ return signalStrength.getLevel();
}
}
@@ -501,11 +709,7 @@
checkDefaultData();
mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null;
if (mCurrentState.connected) {
- if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
- mCurrentState.level = getCdmaLevel();
- } else {
- mCurrentState.level = mSignalStrength.getLevel();
- }
+ mCurrentState.level = getSignalLevel(mSignalStrength);
}
String iconKey = getIconKey(mTelephonyDisplayInfo);
@@ -577,7 +781,13 @@
}
private void recordLastMobileStatus(String mobileStatus) {
- mMobileStatusHistory[mMobileStatusHistoryIndex++ & (HISTORY_SIZE - 1)] = mobileStatus;
+ mMobileStatusHistory[mMobileStatusHistoryIndex] = mobileStatus;
+ mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE;
+ }
+
+ @VisibleForTesting
+ void setImsType(int imsType) {
+ mImsType = imsType;
}
@Override
@@ -592,15 +802,17 @@
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
pw.println(" MobileStatusHistory");
int size = 0;
- for (int i = 0; i < HISTORY_SIZE; i++) {
- if (mMobileStatusHistory[i] != null) size++;
+ for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
+ if (mMobileStatusHistory[i] != null) {
+ size++;
+ }
}
// Print out the previous states in ordered number.
- for (int i = mMobileStatusHistoryIndex + HISTORY_SIZE - 1;
- i >= mMobileStatusHistoryIndex + HISTORY_SIZE - size; i--) {
+ for (int i = mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - 1;
+ i >= mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - size; i--) {
pw.println(" Previous MobileStatus("
- + (mMobileStatusHistoryIndex + HISTORY_SIZE - i) + "): "
- + mMobileStatusHistory[i & (HISTORY_SIZE - 1)]);
+ + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
+ + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index e60d5c5..0a9fead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -53,7 +53,7 @@
/**
* Callback for listeners to be able to update the state of any UI tracking connectivity
- * @param statusIcon the icon that should be shown in the status bar
+ * @param statusIcon the icon that should be shown in the status bar
* @param qsIcon the icon to show in Quick Settings
* @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar
* @param qsType similar to above, the resId of the data type icon to show in Quick Settings
@@ -95,11 +95,11 @@
boolean noNetworksAvailable) {}
/**
- * Callback for listeners to be able to update the no calling & SMS status
- * @param noCalling whether the calling and SMS is not working.
+ * Callback for listeners to be able to update the call indicator
+ * @param statusIcon the icon for the call indicator
* @param subId subscription ID for which to update the UI
*/
- default void setNoCallingStatus(boolean noCalling, int subId) {}
+ default void setCallIndicator(IconState statusIcon, int subId) {}
}
public interface EmergencyListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 80c7811..9f92142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -554,6 +554,20 @@
return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
}
+ void notifyWifiLevelChange(int level) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+ mobileSignalController.notifyWifiLevelChange(level);
+ }
+ }
+
+ void notifyDefaultMobileLevelChange(int level) {
+ for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+ MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+ mobileSignalController.notifyDefaultMobileLevelChange(level);
+ }
+ }
+
private void notifyControllersMobileDataChanged() {
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
@@ -623,6 +637,9 @@
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
+ if (mProviderModel) {
+ mobileSignalController.refreshCallIndicator(cb);
+ }
}
mCallbackHandler.setListening(cb, true);
}
@@ -1272,7 +1289,8 @@
}
private void recordLastNetworkCallback(String callback) {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+ mHistory[mHistoryIndex] = callback;
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 554145e..4b6722c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -23,6 +23,7 @@
import com.android.settingslib.SignalIcon.IconGroup;
import com.android.settingslib.SignalIcon.State;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.io.PrintWriter;
@@ -167,8 +168,8 @@
}
}
- protected final void notifyNoCallingStatusChange(boolean noCalling, int subId) {
- mCallbackHandler.setNoCallingStatus(noCalling, subId);
+ protected final void notifyCallStateChange(IconState statusIcon, int subId) {
+ mCallbackHandler.setCallIndicator(statusIcon, subId);
}
/**
@@ -187,7 +188,8 @@
* and last value of any state data.
*/
protected void recordLastState() {
- mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+ mHistory[mHistoryIndex].copyFrom(mLastState);
+ mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
public void dump(PrintWriter pw) {
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 68d74ef..019ab47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -423,7 +423,7 @@
}
}
- private void showExitGuestDialog(int id) {
+ protected void showExitGuestDialog(int id) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -680,11 +680,7 @@
if (item.isAddUser) {
iconRes = R.drawable.ic_add_circle;
} else if (item.isGuest) {
- if (item.isCurrent) {
- iconRes = R.drawable.ic_exit_to_app;
- } else {
- iconRes = R.drawable.ic_avatar_guest_user;
- }
+ iconRes = R.drawable.ic_avatar_guest_user;
} else {
iconRes = R.drawable.ic_avatar_user;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 1fd2ccb..16998d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.io.PrintWriter;
import java.util.Objects;
public class WifiSignalController extends
@@ -202,6 +203,7 @@
mCurrentState.connected = mWifiTracker.connected;
mCurrentState.ssid = mWifiTracker.ssid;
mCurrentState.rssi = mWifiTracker.rssi;
+ notifyWifiLevelChangeIfNecessary(mWifiTracker.level);
mCurrentState.level = mWifiTracker.level;
mCurrentState.statusLabel = mWifiTracker.statusLabel;
mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
@@ -211,6 +213,12 @@
: mUnmergedWifiIconGroup;
}
+ void notifyWifiLevelChangeIfNecessary(int level) {
+ if (level != mCurrentState.level) {
+ mNetworkController.notifyWifiLevelChange(level);
+ }
+ }
+
boolean isCarrierMergedWifi(int subId) {
return mCurrentState.isDefault
&& mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
@@ -225,6 +233,12 @@
notifyListenersIfNecessary();
}
+ @Override
+ public void dump(PrintWriter pw) {
+ super.dump(pw);
+ mWifiTracker.dump(pw);
+ }
+
/**
* Handler to receive the data activity on wifi.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..9e78a66 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,12 +15,15 @@
*/
package com.android.systemui.theme;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
-import android.os.SystemProperties;
+import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -50,13 +53,6 @@
public class ThemeOverlayApplier implements Dumpable {
private static final String TAG = "ThemeOverlayApplier";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean MONET_ENABLED = SystemProperties
- .getBoolean("persist.sysui.monet", false);
-
- @VisibleForTesting
- static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
- @VisibleForTesting
- static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
@VisibleForTesting
static final String ANDROID_PACKAGE = "android";
@@ -65,10 +61,8 @@
@VisibleForTesting
static final String SYSUI_PACKAGE = "com.android.systemui";
- @VisibleForTesting
static final String OVERLAY_CATEGORY_ACCENT_COLOR =
"android.theme.customization.accent_color";
- @VisibleForTesting
static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
"android.theme.customization.system_palette";
@VisibleForTesting
@@ -117,16 +111,6 @@
OVERLAY_CATEGORY_ICON_ANDROID,
OVERLAY_CATEGORY_ICON_SYSUI);
- /**
- * List of main colors of Monet themes. These are extracted from overlays installed
- * on the system.
- */
- private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
- /**
- * Same as above, but providing accent colors instead of a system palette.
- */
- private final ArrayList<Integer> mAccentColors = new ArrayList<>();
-
/* Allowed overlay categories for each target package. */
private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
/* Target package for each overlay category. */
@@ -162,64 +146,17 @@
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
- collectMonetSystemOverlays();
dumpManager.registerDumpable(TAG, this);
}
/**
- * List of accent colors available as Monet overlays.
- */
- List<Integer> getAvailableAccentColors() {
- return mAccentColors;
- }
-
- /**
- * List of main system colors available as Monet overlays.
- */
- List<Integer> getAvailableSystemColors() {
- return mMainSystemColors;
- }
-
- private void collectMonetSystemOverlays() {
- if (!MONET_ENABLED) {
- return;
- }
- List<OverlayInfo> androidOverlays = mOverlayManager
- .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
- for (OverlayInfo overlayInfo : androidOverlays) {
- String packageName = overlayInfo.packageName;
- if (DEBUG) {
- Log.d(TAG, "Processing overlay " + packageName);
- }
- if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
- && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
- try {
- String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
- mMainSystemColors.add(Integer.parseInt(color, 16));
- } catch (NumberFormatException e) {
- Log.w(TAG, "Invalid package name for overlay " + packageName, e);
- }
- } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
- && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
- try {
- String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
- mAccentColors.add(Integer.parseInt(color, 16));
- } catch (NumberFormatException e) {
- Log.w(TAG, "Invalid package name for overlay " + packageName, e);
- }
- } else if (DEBUG) {
- Log.d(TAG, "Unknown overlay: " + packageName + " category: "
- + overlayInfo.category);
- }
- }
- }
-
- /**
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
*/
void applyCurrentUserOverlays(
- Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
+ Map<String, OverlayIdentifier> categoryToPackage,
+ FabricatedOverlay[] pendingCreation,
+ Set<UserHandle> userHandles) {
// Disable all overlays that have not been specified in the user setting.
final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
@@ -229,55 +166,68 @@
final List<OverlayInfo> overlays = new ArrayList<>();
targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
.getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
- final Map<String, String> overlaysToDisable = overlays.stream()
+ final List<Pair<String, String>> overlaysToDisable = overlays.stream()
.filter(o ->
mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
.filter(o -> overlayCategoriesToDisable.contains(o.category))
.filter(o -> o.isEnabled())
- .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
+ .map(o -> new Pair<>(o.category, o.packageName))
+ .collect(Collectors.toList());
+
+ OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+ if (pendingCreation != null) {
+ for (FabricatedOverlay overlay : pendingCreation) {
+ transaction.registerFabricatedOverlay(overlay);
+ }
+ }
// Toggle overlays in the order of THEME_CATEGORIES.
for (String category : THEME_CATEGORIES) {
if (categoryToPackage.containsKey(category)) {
- setEnabled(categoryToPackage.get(category), category, userHandles, true);
- } else if (overlaysToDisable.containsKey(category)) {
- setEnabled(overlaysToDisable.get(category), category, userHandles, false);
+ OverlayIdentifier overlayInfo = categoryToPackage.get(category);
+ setEnabled(transaction, overlayInfo, category, userHandles, true);
}
}
- }
-
- private void setEnabled(
- String packageName, String category, Set<UserHandle> handles, boolean enabled) {
- for (UserHandle userHandle : handles) {
- setEnabledAsync(packageName, userHandle, enabled);
+ for (Pair<String, String> packageToDisable : overlaysToDisable) {
+ OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+ setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
}
- if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
- setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
- }
- }
- private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
mExecutor.execute(() -> {
- if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
try {
- if (enabled) {
- mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
- } else {
- mOverlayManager.setEnabled(pkg, false, userHandle);
- }
+ mOverlayManager.commit(transaction.build());
} catch (SecurityException | IllegalStateException e) {
- Log.e(TAG,
- String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
+ Log.e(TAG, "setEnabled failed", e);
}
});
}
+ @VisibleForTesting
+ protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+ return new OverlayManagerTransaction.Builder();
+ }
+
+ private void setEnabled(OverlayManagerTransaction.Builder transaction,
+ OverlayIdentifier identifier, String category, Set<UserHandle> handles,
+ boolean enabled) {
+ if (DEBUG) {
+ Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
+ + category + ": " + enabled);
+ }
+ for (UserHandle userHandle : handles) {
+ transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
+ }
+ if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
+ transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
+ }
+ }
+
/**
* @inherit
*/
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("mMainSystemColors=" + mMainSystemColors.size());
- pw.println("mAccentColors=" + mAccentColors.size());
+ pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
+ pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d9f4744..522a42b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
@@ -25,6 +26,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Color;
@@ -40,7 +43,6 @@
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -48,6 +50,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
@@ -59,10 +62,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -77,9 +80,12 @@
*/
@SysUISingleton
public class ThemeOverlayController extends SystemUI implements Dumpable {
- private static final String TAG = "ThemeOverlayController";
+ protected static final String TAG = "ThemeOverlayController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected static final int MAIN = 0;
+ protected static final int ACCENT = 1;
+
// If lock screen wallpaper colors should also be considered when selecting the theme.
// Doing this has performance impact, given that overlays would need to be swapped when
// the device unlocks.
@@ -95,16 +101,19 @@
private final Handler mBgHandler;
private final WallpaperManager mWallpaperManager;
private final KeyguardStateController mKeyguardStateController;
+ private final boolean mIsMonetEnabled;
private WallpaperColors mLockColors;
private WallpaperColors mSystemColors;
- // Color extracted from wallpaper, NOT the color used on the overlay
+ // If fabricated overlays were already created for the current theme.
+ private boolean mNeedsOverlayCreation;
+ // Dominant olor extracted from wallpaper, NOT the color used on the overlay
protected int mMainWallpaperColor = Color.TRANSPARENT;
- // Color extracted from wallpaper, NOT the color used on the overlay
+ // Accent color extracted from wallpaper, NOT the color used on the overlay
protected int mWallpaperAccentColor = Color.TRANSPARENT;
- // Main system color that maps to an overlay color
- private int mSystemOverlayColor = Color.TRANSPARENT;
- // Accent color that maps to an overlay color
- private int mAccentOverlayColor = Color.TRANSPARENT;
+ // System colors overlay
+ private FabricatedOverlay mSystemOverlay;
+ // Accent colors overlay
+ private FabricatedOverlay mAccentOverlay;
@Inject
public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -112,9 +121,10 @@
@Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
SecureSettings secureSettings, WallpaperManager wallpaperManager,
UserManager userManager, KeyguardStateController keyguardStateController,
- DumpManager dumpManager) {
+ DumpManager dumpManager, FeatureFlags featureFlags) {
super(context);
+ mIsMonetEnabled = featureFlags.isMonetEnabled();
mBroadcastDispatcher = broadcastDispatcher;
mUserManager = userManager;
mBgExecutor = bgExecutor;
@@ -221,20 +231,16 @@
mMainWallpaperColor = mainColor;
mWallpaperAccentColor = accentCandidate;
- // Let's compare these colors to our finite set of overlays, and then pick an overlay.
- List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
- List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
-
- if (systemColors.size() == 0 || accentColors.size() == 0) {
+ if (mIsMonetEnabled) {
+ mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
+ mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+ mNeedsOverlayCreation = true;
if (DEBUG) {
- Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+ Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
+ + mAccentOverlay);
}
- return;
}
- mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
- mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
-
updateThemeOverlays();
}
@@ -257,42 +263,10 @@
}
/**
- * Given a color and a list of candidates, return the candidate that's the most similar to the
- * given color.
+ * Given a color candidate, return an overlay definition.
*/
- protected int getClosest(List<Integer> candidates, int color) {
- float[] hslMain = new float[3];
- float[] hslCandidate = new float[3];
-
- ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
- hslMain[0] /= 360f;
-
- // To close to white or black, let's use the default system theme instead of
- // applying a colorized one.
- if (hslMain[2] < 0.05 || hslMain[2] > 0.95) {
- return Color.TRANSPARENT;
- }
-
- float minDistance = Float.MAX_VALUE;
- int closestColor = Color.TRANSPARENT;
- for (int candidate: candidates) {
- ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
- hslCandidate);
- hslCandidate[0] /= 360f;
-
- float sqDistance = squared(hslCandidate[0] - hslMain[0])
- + squared(hslCandidate[1] - hslMain[1])
- + squared(hslCandidate[2] - hslMain[2]);
- if (sqDistance < minDistance) {
- minDistance = sqDistance;
- closestColor = candidate;
- }
- }
- return closestColor;
- }
-
- private static float squared(float f) {
- return f * f;
+ protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
+ return null;
}
private void updateThemeOverlays() {
@@ -301,20 +275,15 @@
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
currentUser);
if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
- boolean hasSystemPalette = false;
- boolean hasAccentColor = false;
- final Map<String, String> categoryToPackage = new ArrayMap<>();
+ final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
if (!TextUtils.isEmpty(overlayPackageJson)) {
try {
JSONObject object = new JSONObject(overlayPackageJson);
for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
if (object.has(category)) {
- if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
- hasAccentColor = true;
- } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
- hasSystemPalette = true;
- }
- categoryToPackage.put(category, object.getString(category));
+ OverlayIdentifier identifier =
+ new OverlayIdentifier(object.getString(category));
+ categoryToPackage.put(category, identifier);
}
}
} catch (JSONException e) {
@@ -322,17 +291,41 @@
}
}
- // Let's apply the system palette, but only if it was not overridden by the style picker.
- if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
- categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
- ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
- + getColorString(mSystemOverlayColor));
+ // Let's generate system overlay if the style picker decided to override it.
+ OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
+ try {
+ int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
+ mSystemOverlay = getOverlay(color, MAIN);
+ mNeedsOverlayCreation = true;
+ categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName());
+ }
}
- // Same for the accent color
- if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
- categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
- ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
- + getColorString(mAccentOverlayColor));
+
+ // Same for accent color.
+ OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
+ if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
+ try {
+ int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
+ mAccentOverlay = getOverlay(color, ACCENT);
+ mNeedsOverlayCreation = true;
+ categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName());
+ }
+ }
+
+ // Compatibility with legacy themes, where full packages were defined, instead of just
+ // colors.
+ if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
+ && mSystemOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+ }
+ if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
+ && mAccentOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
}
Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -341,28 +334,31 @@
userHandles.add(userInfo.getUserHandle());
}
}
- mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
- }
-
- private String getColorString(int color) {
- String colorString = Integer.toHexString(color).toUpperCase();
- while (colorString.length() < 6) {
- colorString = "0" + colorString;
+ if (DEBUG) {
+ Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
+ .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
+ Collectors.joining(", ")));
}
- // Remove alpha component
- if (colorString.length() > 6) {
- colorString = colorString.substring(colorString.length() - 6);
+ if (mNeedsOverlayCreation) {
+ mNeedsOverlayCreation = false;
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+ mSystemOverlay, mAccentOverlay
+ }, userHandles);
+ } else {
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
}
- return colorString;
}
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
pw.println("mLockColors=" + mLockColors);
pw.println("mSystemColors=" + mSystemColors);
pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
- pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
- pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+ pw.println("mSystemOverlayColor=" + mSystemOverlay);
+ pw.println("mAccentOverlayColor=" + mAccentOverlay);
+ pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+ pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index e9fcf1a..365cd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -20,10 +20,21 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.ToastPresenter;
import com.android.internal.R;
+import com.android.launcher3.icons.IconFactory;
import com.android.systemui.plugins.ToastPlugin;
/**
@@ -35,23 +46,43 @@
final CharSequence mText;
final ToastPlugin.Toast mPluginToast;
- final int mDefaultGravity;
- final int mDefaultY;
+ private final String mPackageName;
+ private final int mUserId;
+ private final LayoutInflater mLayoutInflater;
+ private final boolean mToastStyleEnabled;
+
final int mDefaultX = 0;
final int mDefaultHorizontalMargin = 0;
final int mDefaultVerticalMargin = 0;
- SystemUIToast(Context context, CharSequence text) {
- this(context, text, null);
+ private int mDefaultY;
+ private int mDefaultGravity;
+
+ @NonNull private final View mToastView;
+ @Nullable private final Animator mInAnimator;
+ @Nullable private final Animator mOutAnimator;
+
+ SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+ String packageName, int userId, boolean toastStyleEnabled, int orientation) {
+ this(layoutInflater, context, text, null, packageName, userId,
+ toastStyleEnabled, orientation);
}
- SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+ SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+ ToastPlugin.Toast pluginToast, String packageName, int userId,
+ boolean toastStyleEnabled, int orientation) {
+ mToastStyleEnabled = toastStyleEnabled;
+ mLayoutInflater = layoutInflater;
mContext = context;
mText = text;
mPluginToast = pluginToast;
+ mPackageName = packageName;
+ mUserId = userId;
+ mToastView = inflateToastView();
+ mInAnimator = createInAnimator();
+ mOutAnimator = createOutAnimator();
- mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
- mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+ onOrientationChange(orientation);
}
@Override
@@ -102,28 +133,19 @@
@Override
@NonNull
public View getView() {
- if (isPluginToast() && mPluginToast.getView() != null) {
- return mPluginToast.getView();
- }
- return ToastPresenter.getTextToastView(mContext, mText);
+ return mToastView;
}
@Override
@Nullable
public Animator getInAnimation() {
- if (isPluginToast() && mPluginToast.getInAnimation() != null) {
- return mPluginToast.getInAnimation();
- }
- return null;
+ return mInAnimator;
}
@Override
@Nullable
public Animator getOutAnimation() {
- if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
- return mPluginToast.getOutAnimation();
- }
- return null;
+ return mOutAnimator;
}
/**
@@ -136,4 +158,80 @@
private boolean isPluginToast() {
return mPluginToast != null;
}
+
+ private View inflateToastView() {
+ if (isPluginToast() && mPluginToast.getView() != null) {
+ return mPluginToast.getView();
+ }
+
+ View toastView;
+ if (mToastStyleEnabled) {
+ toastView = mLayoutInflater.inflate(
+ com.android.systemui.R.layout.text_toast, null);
+ ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
+
+ ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
+ .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId));
+ } else {
+ toastView = ToastPresenter.getTextToastView(mContext, mText);
+ }
+
+ return toastView;
+ }
+
+ /**
+ * Called on orientation changes to update parameters associated with the toast placement.
+ */
+ public void onOrientationChange(int orientation) {
+ if (mPluginToast != null) {
+ mPluginToast.onOrientationChange(orientation);
+ }
+
+ mDefaultY = mContext.getResources().getDimensionPixelSize(
+ mToastStyleEnabled
+ ? com.android.systemui.R.dimen.toast_y_offset
+ : R.dimen.toast_y_offset);
+ mDefaultGravity =
+ mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+ }
+
+ private Animator createInAnimator() {
+ if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+ return mPluginToast.getInAnimation();
+ }
+
+ return mToastStyleEnabled
+ ? ToastDefaultAnimation.Companion.toastIn(getView())
+ : null;
+ }
+
+ private Animator createOutAnimator() {
+ if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+ return mPluginToast.getOutAnimation();
+ }
+ return mToastStyleEnabled
+ ? ToastDefaultAnimation.Companion.toastOut(getView())
+ : null;
+ }
+
+ /**
+ * Get badged app icon if necessary, similar as used in the Settings UI.
+ * @return The icon to use
+ */
+ public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
+ int userId) {
+ final PackageManager packageManager = context.getPackageManager();
+ try {
+ final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+ IconFactory iconFactory = IconFactory.obtain(context);
+ Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+ appInfo.loadUnbadgedIcon(packageManager), user, false).icon;
+ return new BitmapDrawable(context.getResources(), iconBmp);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e);
+ return null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
new file mode 100644
index 0000000..603d690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.toast
+
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.animation.AnimatorSet
+
+class ToastDefaultAnimation {
+ /**
+ * sum of the in and out animation durations cannot exceed
+ * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast
+ * window from being removed before animations are completed
+ */
+ companion object {
+ // total duration shouldn't exceed NotificationManagerService's delay for "in" animation
+ fun toastIn(view: View): AnimatorSet? {
+ val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+ val text: View? = view.findViewById(com.android.systemui.R.id.text)
+ if (icon == null || text == null) {
+ return null
+ }
+ val linearInterp = LinearInterpolator()
+ val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f)
+ val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply {
+ interpolator = scaleInterp
+ duration = 333
+ }
+ val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply {
+ interpolator = scaleInterp
+ duration = 333
+ }
+ val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 66
+ }
+ text.alpha = 0f // Set now otherwise won't apply until start delay
+ val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 283
+ startDelay = 50
+ }
+ icon.alpha = 0f // Set now otherwise won't apply until start delay
+ val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply {
+ interpolator = linearInterp
+ duration = 283
+ startDelay = 50
+ }
+ return AnimatorSet().apply {
+ playTogether(sX, sY, vA, tA, iA)
+ }
+ }
+
+ fun toastOut(view: View): AnimatorSet? {
+ // total duration shouldn't exceed NotificationManagerService's delay for "out" anim
+ val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+ val text: View? = view.findViewById(com.android.systemui.R.id.text)
+ if (icon == null || text == null) {
+ return null
+ }
+ val linearInterp = LinearInterpolator()
+ val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f)
+ val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply {
+ interpolator = scaleInterp
+ duration = 250
+ }
+ val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply {
+ interpolator = scaleInterp
+ duration = 250
+ }
+ val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 100
+ startDelay = 150
+ }
+ val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 166
+ }
+ val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply {
+ interpolator = linearInterp
+ duration = 166
+ }
+ return AnimatorSet().apply {
+ playTogether(sX, sY, vA, tA, iA)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index d8cb61c..8b782d4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -17,6 +17,7 @@
package com.android.systemui.toast;
import android.content.Context;
+import android.view.LayoutInflater;
import androidx.annotation.NonNull;
@@ -26,6 +27,7 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.ToastPlugin;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -40,10 +42,18 @@
public class ToastFactory implements Dumpable {
// only one ToastPlugin can be connected at a time.
private ToastPlugin mPlugin;
+ private final LayoutInflater mLayoutInflater;
+ private final boolean mToastStyleEnabled;
@Inject
- public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+ public ToastFactory(
+ LayoutInflater layoutInflater,
+ PluginManager pluginManager,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
+ mLayoutInflater = layoutInflater;
dumpManager.registerDumpable("ToastFactory", this);
+ mToastStyleEnabled = featureFlags.isToastStyleEnabled();
pluginManager.addPluginListener(
new PluginListener<ToastPlugin>() {
@Override
@@ -64,11 +74,13 @@
* Create a toast to be shown by ToastUI.
*/
public SystemUIToast createToast(Context context, CharSequence text, String packageName,
- int userId) {
+ int userId, int orientation) {
if (isPluginAvailable()) {
- return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+ return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+ packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
}
- return new SystemUIToast(context, text);
+ return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+ mToastStyleEnabled, orientation);
}
private boolean isPluginAvailable() {
@@ -79,5 +91,6 @@
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("ToastFactory:");
pw.println(" mAttachedPlugin=" + mPlugin);
+ pw.println(" mToastStyleEnabled=" + mToastStyleEnabled);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 78173cf..51541bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -49,6 +49,15 @@
})
}
+ fun logOrientationChange(text: String, isPortrait: Boolean) {
+ log(DEBUG, {
+ str1 = text
+ bool1 = isPortrait
+ }, {
+ "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1"
+ })
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 409d136..92ea1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,27 +16,28 @@
package com.android.systemui.toast;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import android.animation.Animator;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
-import android.widget.Toast;
import android.widget.ToastPresenter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Objects;
@@ -58,18 +59,19 @@
private final IAccessibilityManager mIAccessibilityManager;
private final AccessibilityManager mAccessibilityManager;
private final ToastFactory mToastFactory;
- private final DelayableExecutor mMainExecutor;
private final ToastLogger mToastLogger;
private SystemUIToast mToast;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
+ private ToastOutAnimatorListener mToastOutAnimatorListener;
+
+ private int mOrientation = ORIENTATION_PORTRAIT;
@Inject
public ToastUI(
Context context,
CommandQueue commandQueue,
ToastFactory toastFactory,
- @Main DelayableExecutor mainExecutor,
ToastLogger toastLogger) {
this(context, commandQueue,
INotificationManager.Stub.asInterface(
@@ -77,21 +79,19 @@
IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
toastFactory,
- mainExecutor,
toastLogger);
}
@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
@Nullable IAccessibilityManager accessibilityManager,
- ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+ ToastFactory toastFactory, ToastLogger toastLogger
) {
super(context);
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
mIAccessibilityManager = accessibilityManager;
mToastFactory = toastFactory;
- mMainExecutor = mainExecutor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mToastLogger = toastLogger;
}
@@ -105,36 +105,38 @@
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,
IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
- if (mPresenter != null) {
- hideCurrentToast();
- }
- UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
- Context context = mContext.createContextAsUser(userHandle, 0);
- mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+ Runnable showToastRunnable = () -> {
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ Context context = mContext.createContextAsUser(userHandle, 0);
+ mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+ userHandle.getIdentifier(), mOrientation);
- if (mToast.hasCustomAnimation()) {
if (mToast.getInAnimation() != null) {
mToast.getInAnimation().start();
}
- final Animator hideAnimator = mToast.getOutAnimation();
- if (hideAnimator != null) {
- final long durationMillis = duration == Toast.LENGTH_LONG
- ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
- final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
- (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
- mMainExecutor.executeDelayed(() -> hideAnimator.start(),
- updatedDuration - hideAnimator.getTotalDuration());
- }
+
+ mCallback = callback;
+ mPresenter = new ToastPresenter(context, mIAccessibilityManager,
+ mNotificationManager, packageName);
+ // Set as trusted overlay so touches can pass through toasts
+ mPresenter.getLayoutParams().setTrustedOverlay();
+ mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+ mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+ mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
+ };
+
+ if (mToastOutAnimatorListener != null) {
+ // if we're currently animating out a toast, show new toast after prev toast is hidden
+ mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable);
+ } else if (mPresenter != null) {
+ // if there's a toast already showing that we haven't tried hiding yet, hide it and
+ // then show the next toast after its hidden animation is done
+ hideCurrentToast(showToastRunnable);
+ } else {
+ // else, show this next toast immediately
+ showToastRunnable.run();
}
- mCallback = callback;
- mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
- packageName);
- // Set as trusted overlay so touches can pass through toasts
- mPresenter.getLayoutParams().setTrustedOverlay();
- mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
- mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
- mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
- mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
}
@Override
@@ -146,12 +148,88 @@
return;
}
mToastLogger.logOnHideToast(packageName, token.toString());
- hideCurrentToast();
+ hideCurrentToast(null);
}
@MainThread
- private void hideCurrentToast() {
- mPresenter.hide(mCallback);
+ private void hideCurrentToast(Runnable runnable) {
+ if (mToast.getOutAnimation() != null) {
+ Animator animator = mToast.getOutAnimation();
+ mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
+ runnable);
+ animator.addListener(mToastOutAnimatorListener);
+ animator.start();
+ } else {
+ mPresenter.hide(mCallback);
+ if (runnable != null) {
+ runnable.run();
+ }
+ }
+ mToast = null;
mPresenter = null;
+ mCallback = null;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ if (newConfig.orientation != mOrientation) {
+ mOrientation = newConfig.orientation;
+ if (mToast != null) {
+ mToastLogger.logOrientationChange(mToast.mText.toString(),
+ mOrientation == ORIENTATION_PORTRAIT);
+ mToast.onOrientationChange(mOrientation);
+ mPresenter.updateLayoutParams(
+ mToast.getXOffset(),
+ mToast.getYOffset(),
+ mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(),
+ mToast.getGravity());
+ }
+ }
+ }
+
+ /**
+ * Once the out animation for a toast is finished, start showing the next toast.
+ */
+ class ToastOutAnimatorListener implements Animator.AnimatorListener {
+ final ToastPresenter mPrevPresenter;
+ final ITransientNotificationCallback mPrevCallback;
+ @Nullable Runnable mShowNextToastRunnable;
+
+ ToastOutAnimatorListener(
+ @NonNull ToastPresenter presenter,
+ @NonNull ITransientNotificationCallback callback,
+ @Nullable Runnable runnable) {
+ mPrevPresenter = presenter;
+ mPrevCallback = callback;
+ mShowNextToastRunnable = runnable;
+ }
+
+ void setShowNextToastRunnable(Runnable runnable) {
+ mShowNextToastRunnable = runnable;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPrevPresenter.hide(mPrevCallback);
+ if (mShowNextToastRunnable != null) {
+ mShowNextToastRunnable.run();
+ }
+ mToastOutAnimatorListener = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onAnimationEnd(animation);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 09335af..b67574d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -109,7 +109,7 @@
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
(OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(R.string.qs_customize_remove), new OnClickListener() {
+ context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Tell the tuner (in main SysUI process) to clear all its settings.
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 72f1f22..fd3641c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -20,12 +20,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
+import com.android.systemui.R;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import java.util.HashSet;
import java.util.List;
@@ -163,4 +166,15 @@
}
return apps;
}
+
+ /**
+ * Returns true if the device should use the split notification shade, based on feature flags,
+ * orientation and screen width.
+ */
+ public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
+ Resources resources) {
+ return featureFlags.isTwoColumnNotificationShadeEnabled()
+ && resources.getBoolean(R.bool.config_use_split_notification_shade);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 6e7aed0..afeda967 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -45,6 +45,7 @@
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import androidx.annotation.NonNull;
@@ -86,10 +87,12 @@
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
@@ -248,38 +251,19 @@
});
mSysuiProxy = new Bubbles.SysuiProxy() {
- private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
- Class clazz) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- return runnable.get();
- }
- final T[] result = (T[]) Array.newInstance(clazz, 1);
- final CountDownLatch latch = new CountDownLatch(1);
- executor.execute(() -> {
- result[0] = runnable.get();
- latch.countDown();
- });
- try {
- latch.await();
- return result[0];
- } catch (InterruptedException e) {
- return null;
- }
- }
-
@Override
- @Nullable
- public BubbleEntry getPendingOrActiveEntry(String key) {
- return executeBlockingForResult(() -> {
+ public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) {
+ sysuiMainExecutor.execute(() -> {
NotificationEntry entry =
mNotificationEntryManager.getPendingOrActiveNotif(key);
- return entry == null ? null : notifToBubbleEntry(entry);
- }, sysuiMainExecutor, BubbleEntry.class);
+ callback.accept(entry == null ? null : notifToBubbleEntry(entry));
+ });
}
@Override
- public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
- return executeBlockingForResult(() -> {
+ public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ Consumer<List<BubbleEntry>> callback) {
+ sysuiMainExecutor.execute(() -> {
List<BubbleEntry> result = new ArrayList<>();
List<NotificationEntry> activeEntries =
mNotificationEntryManager.getActiveNotificationsForCurrentUser();
@@ -291,27 +275,8 @@
result.add(notifToBubbleEntry(entry));
}
}
- return result;
- }, sysuiMainExecutor, List.class);
- }
-
- @Override
- public boolean isNotificationShadeExpand() {
- return executeBlockingForResult(() -> {
- return mNotificationShadeWindowController.getPanelExpanded();
- }, sysuiMainExecutor, Boolean.class);
- }
-
- @Override
- public boolean shouldBubbleUp(String key) {
- return executeBlockingForResult(() -> {
- final NotificationEntry entry =
- mNotificationEntryManager.getPendingOrActiveNotif(key);
- if (entry != null) {
- return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
- }
- return false;
- }, sysuiMainExecutor, Boolean.class);
+ callback.accept(result);
+ });
}
@Override
@@ -587,7 +552,20 @@
}
void onRankingUpdate(RankingMap rankingMap) {
- mBubbles.onRankingUpdated(rankingMap);
+ String[] orderedKeys = rankingMap.getOrderedKeys();
+ HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>();
+ for (int i = 0; i < orderedKeys.length; i++) {
+ String key = orderedKeys[i];
+ NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+ BubbleEntry bubbleEntry = entry != null
+ ? notifToBubbleEntry(entry)
+ : null;
+ boolean shouldBubbleUp = entry != null
+ ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+ : false;
+ pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
+ }
+ mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Handler;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
import dagger.Provides;
/**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
*/
@Module(includes = {WMShellBaseModule.class})
public abstract class TvPipModule {
@@ -143,7 +145,8 @@
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+ Optional<LegacySplitScreenController> splitScreenOptional,
+ DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.view.IWindowManager;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
import dagger.Provides;
/**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation. Common
+ * dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = {TvPipModule.class})
public class TvWMShellModule {
+
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
+
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
transactionPool);
}
+ //
+ // Split/multiwindow
+ //
+
@WMSingleton
@Provides
- static LegacySplitScreen provideSplitScreen(Context context,
+ static LegacySplitScreenController provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor,
@ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return LegacySplitScreenController.create(context, displayController, systemWindows,
+ return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..ec61db5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
import javax.inject.Inject;
/**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components. This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ * -> SystemUIFactory is initialized
+ * -> WMComponent is created
+ * -> WMShellBaseModule dependencies are injected
+ * -> WMShellModule (form-factory specific) dependencies are injected
+ * -> SysUIComponent is created
+ * -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ * -> SysUI services are started
+ * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
*/
@SysUISingleton
public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
@Override
public void start() {
+ // TODO: Consider piping config change and other common calls to a shell component to
+ // delegate internally
mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b42dde6..449db61 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,6 +32,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +46,7 @@
import com.android.wm.shell.TaskViewFactoryController;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
@@ -63,6 +65,7 @@
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -70,8 +73,8 @@
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +88,13 @@
import dagger.Provides;
/**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
@Module
public abstract class WMShellBaseModule {
@@ -174,53 +182,9 @@
}
}
- @WMSingleton
- @Provides
- static ShellInit provideShellInit(DisplayImeController displayImeController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairs> appPairsOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor) {
- return ShellInitImpl.create(displayImeController,
- dragAndDropController,
- shellTaskOrganizer,
- legacySplitScreenOptional,
- splitScreenOptional,
- appPairsOptional,
- fullscreenTaskListener,
- transitions,
- mainExecutor);
- }
-
- /**
- * Note, this is only optional because we currently pass this to the SysUI component scope and
- * for non-primary users, we may inject a null-optional for that dependency.
- */
- @WMSingleton
- @Provides
- static Optional<ShellCommandHandler> provideShellCommandHandler(
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreen> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutout,
- Optional<AppPairs> appPairsOptional,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
- legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, appPairsOptional, mainExecutor));
- }
-
- @WMSingleton
- @Provides
- static TransactionPool provideTransactionPool() {
- return new TransactionPool();
- }
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
@WMSingleton
@Provides
@@ -238,8 +202,45 @@
@WMSingleton
@Provides
- static FloatingContentCoordinator provideFloatingContentCoordinator() {
- return new FloatingContentCoordinator();
+ static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+ Context context, SizeCompatUIController sizeCompatUI) {
+ return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+ }
+
+ @WMSingleton
+ @Provides
+ static SizeCompatUIController provideSizeCompatUIController(Context context,
+ DisplayController displayController, DisplayImeController imeController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SyncTransactionQueue(pool, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static SystemWindows provideSystemWindows(DisplayController displayController,
+ IWindowManager wmService) {
+ return new SystemWindows(displayController, wmService);
+ }
+
+ // We currently dedupe multiple messages, so we use the shell main handler directly
+ @WMSingleton
+ @Provides
+ static TaskStackListenerImpl providerTaskStackListenerImpl(
+ @ShellMainThread Handler mainHandler) {
+ return new TaskStackListenerImpl(mainHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static TransactionPool provideTransactionPool() {
+ return new TransactionPool();
}
@WMSingleton
@@ -249,10 +250,99 @@
return new WindowManagerShellWrapper(mainExecutor);
}
+ //
+ // Bubbles
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+ return bubbleController.map((controller) -> controller.asBubbles());
+ }
+
+ // Note: Handler needed for LauncherApps.register
+ @WMSingleton
+ @Provides
+ static Optional<BubbleController> provideBubbleController(Context context,
+ FloatingContentCoordinator floatingContentCoordinator,
+ IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ ShellTaskOrganizer organizer,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return Optional.of(BubbleController.create(context, null /* synchronizer */,
+ floatingContentCoordinator, statusBarService, windowManager,
+ windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+ mainExecutor, mainHandler));
+ }
+
+ //
+ // Fullscreen
+ //
+
+ @WMSingleton
+ @Provides
+ static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ return new FullscreenTaskListener(syncQueue);
+ }
+
+ //
+ // Hide display cutout
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<HideDisplayCutout> provideHideDisplayCutout(
+ Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+ return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+ }
+
+ @WMSingleton
+ @Provides
+ static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+ DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+ return Optional.ofNullable(
+ HideDisplayCutoutController.create(context, displayController, mainExecutor));
+ }
+
+ //
+ // One handed mode (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+ return oneHandedController.map((controller) -> controller.asOneHanded());
+ }
+
+ // Needs the shell main handler for ContentObserver callbacks
+ @WMSingleton
+ @Provides
+ static Optional<OneHandedController> provideOneHandedController(Context context,
+ DisplayController displayController, TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return Optional.ofNullable(OneHandedController.create(context, displayController,
+ taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+ }
+
+ //
+ // Pip (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static FloatingContentCoordinator provideFloatingContentCoordinator() {
+ return new FloatingContentCoordinator();
+ }
+
@WMSingleton
@Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
- IActivityManager activityManager,
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +358,38 @@
@WMSingleton
@Provides
- static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
- PackageManager packageManager) {
- return new PipUiEventLogger(uiEventLogger, packageManager);
- }
-
- @WMSingleton
- @Provides
static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
@WMSingleton
@Provides
- static SystemWindows provideSystemWindows(DisplayController displayController,
- IWindowManager wmService) {
- return new SystemWindows(displayController, wmService);
+ static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+ PackageManager packageManager) {
+ return new PipUiEventLogger(uiEventLogger, packageManager);
+ }
+
+ //
+ // Shell transitions
+ //
+
+ @WMSingleton
+ @Provides
+ static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+ return Transitions.asRemoteTransitions(transitions);
}
@WMSingleton
@Provides
- static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new SyncTransactionQueue(pool, mainExecutor);
+ static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor) {
+ return new Transitions(organizer, pool, mainExecutor, animExecutor);
}
- @WMSingleton
- @Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
- Context context, SizeCompatUI sizeCompatUI) {
- return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
- }
+ //
+ // Split/multiwindow
+ //
@WMSingleton
@Provides
@@ -307,17 +398,6 @@
return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
}
- // We currently dedupe multiple messages, so we use the shell main handler directly
- @WMSingleton
- @Provides
- static TaskStackListenerImpl providerTaskStackListenerImpl(
- @ShellMainThread Handler mainHandler) {
- return new TaskStackListenerImpl(mainHandler);
- }
-
- @BindsOptionalOf
- abstract LegacySplitScreen optionalLegacySplitScreen();
-
@WMSingleton
@Provides
static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +420,91 @@
}
}
+ // Legacy split (optional feature)
+
+ @WMSingleton
+ @Provides
+ static Optional<LegacySplitScreen> provideLegacySplitScreen(
+ Optional<LegacySplitScreenController> splitScreenController) {
+ return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+ }
+
@BindsOptionalOf
- abstract AppPairs optionalAppPairs();
+ abstract LegacySplitScreenController optionalLegacySplitScreenController();
- // Note: Handler needed for LauncherApps.register
+ // App Pairs (optional feature)
+
@WMSingleton
@Provides
- static Optional<Bubbles> provideBubbles(Context context,
- FloatingContentCoordinator floatingContentCoordinator,
- IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- UiEventLogger uiEventLogger,
- ShellTaskOrganizer organizer,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return Optional.of(BubbleController.create(context, null /* synchronizer */,
- floatingContentCoordinator, statusBarService, windowManager,
- windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
- mainExecutor, mainHandler));
+ static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+ return appPairsController.map((controller) -> controller.asAppPairs());
}
- // Needs the shell main handler for ContentObserver callbacks
+ @BindsOptionalOf
+ abstract AppPairsController optionalAppPairs();
+
+ //
+ // Task view factory
+ //
+
@WMSingleton
@Provides
- static Optional<OneHanded> provideOneHandedController(Context context,
- DisplayController displayController, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return Optional.ofNullable(OneHandedController.create(context, displayController,
- taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+ static Optional<TaskViewFactory> provideTaskViewFactory(
+ TaskViewFactoryController taskViewFactoryController) {
+ return Optional.of(taskViewFactoryController.asTaskViewFactory());
}
@WMSingleton
@Provides
- static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
- DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(
- HideDisplayCutoutController.create(context, displayController, mainExecutor));
- }
-
- @WMSingleton
- @Provides
- static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+ static TaskViewFactoryController provideTaskViewFactoryController(
+ ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
- .getTaskViewFactory());
+ return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
}
+ //
+ // Misc
+ //
+
@WMSingleton
@Provides
- static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
- return new FullscreenTaskListener(syncQueue);
- }
-
- @WMSingleton
- @Provides
- static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
- return Transitions.asRemoteTransitions(transitions);
- }
-
- @WMSingleton
- @Provides
- static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, mainExecutor, animExecutor);
- }
-
- @WMSingleton
- @Provides
- static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
- DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
- return SizeCompatUIController.create(context, displayController, imeController,
+ static ShellInit provideShellInit(DisplayImeController displayImeController,
+ DragAndDropController dragAndDropController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<AppPairsController> appPairsOptional,
+ FullscreenTaskListener fullscreenTaskListener,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return ShellInitImpl.create(displayImeController,
+ dragAndDropController,
+ shellTaskOrganizer,
+ legacySplitScreenOptional,
+ splitScreenOptional,
+ appPairsOptional,
+ fullscreenTaskListener,
+ transitions,
mainExecutor);
}
+
+ /**
+ * Note, this is only optional because we currently pass this to the SysUI component scope and
+ * for non-primary users, we may inject a null-optional for that dependency.
+ */
+ @WMSingleton
+ @Provides
+ static Optional<ShellCommandHandler> provideShellCommandHandler(
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<LegacySplitScreenController> legacySplitScreenOptional,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<Pip> pipOptional,
+ Optional<OneHandedController> oneHandedOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutout,
+ Optional<AppPairsController> appPairsOptional,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+ legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+ hideDisplayCutout, appPairsOptional, mainExecutor));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
import android.os.Handler;
import android.view.IWindowManager;
+import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
import dagger.Provides;
/**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation. Common
+ * dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = WMShellBaseModule.class)
public class WMShellModule {
+
+ //
+ // Internal common - Components used internally by multiple shell features
+ //
+
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
transactionPool);
}
+ //
+ // Split/multiwindow
+ //
+
@WMSingleton
@Provides
- static LegacySplitScreen provideLegacySplitScreen(Context context,
+ static LegacySplitScreenController provideLegacySplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor,
@ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return LegacySplitScreenController.create(context, displayController, systemWindows,
+ return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
@WMSingleton
@Provides
- static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+ static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
- return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+ return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
mainExecutor);
}
+ //
+ // Pip
+ //
+
@WMSingleton
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
PipAnimationController pipAnimationController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
PipTransitionController pipTransitionController,
- Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+ Optional<LegacySplitScreenController> splitScreenOptional,
+ DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
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
new file mode 100644
index 0000000..9278570
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+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
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlActionCoordinatorImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var lazyUiController: Lazy<ControlsUiController>
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var bgExecutor: DelayableExecutor
+ @Mock
+ private lateinit var uiExecutor: DelayableExecutor
+ @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
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+
+ private val ID = "id"
+ }
+
+ private lateinit var coordinator: ControlActionCoordinatorImpl
+ private lateinit var action: ControlActionCoordinatorImpl.Action
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ coordinator = spy(ControlActionCoordinatorImpl(
+ mContext,
+ bgExecutor,
+ uiExecutor,
+ activityStarter,
+ keyguardStateController,
+ globalActionsComponent,
+ taskViewFactory,
+ getFakeBroadcastDispatcher(),
+ lazyUiController
+ ))
+
+ `when`(cvh.cws.ci.controlId).thenReturn(ID)
+ action = spy(coordinator.Action(ID, {}, false))
+ doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
+ }
+
+ @Test
+ fun testToggleRunsWhenUnlocked() {
+ `when`(keyguardStateController.isShowing()).thenReturn(false)
+
+ coordinator.toggle(cvh, "", true)
+ verify(coordinator).bouncerOrRun(action)
+ verify(action).invoke()
+ }
+
+ @Test
+ fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
+ `when`(keyguardStateController.isShowing()).thenReturn(true)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+
+ coordinator.toggle(cvh, "", true)
+ verify(coordinator).bouncerOrRun(action)
+ verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ verify(action, never()).invoke()
+
+ // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
+ reset(action)
+ coordinator.runPendingAction(ID)
+ verify(action, never()).invoke()
+
+ `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+ reset(action)
+ coordinator.runPendingAction(ID)
+ verify(action).invoke()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
index a52a598..0457100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
@@ -19,7 +19,7 @@
import android.app.Activity;
import android.os.Bundle;
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
/**
* Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 2db224f..e88c728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -315,10 +315,9 @@
}
mediaDataManager.onNotificationAdded(KEY, notif)
- // THEN it loads and uses the default background color
+ // THEN it still loads
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
- assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0dc268a..fb817ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +30,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -37,6 +41,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
@@ -44,6 +49,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.animation.DisappearParameters;
import org.junit.Before;
@@ -86,17 +92,25 @@
QSTileView mQSTileView;
@Mock
PagedTileLayout mPagedTileLayout;
+ @Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
+ Resources mResources;
+ @Mock
+ Configuration mConfiguration;
private QSPanelControllerBase<QSPanel> mController;
+
+
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager) {
+ DumpManager dumpManager, FeatureFlags featureFlags) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager);
+ qsLogger, dumpManager, featureFlags);
}
@Override
@@ -121,10 +135,12 @@
when(mQSTileRevealControllerFactory.create(any(), any()))
.thenReturn(mQSTileRevealController);
when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+ when(mQSPanel.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
mController.init();
reset(mQSTileRevealController);
@@ -136,7 +152,7 @@
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
mQSTileHost, mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -218,4 +234,18 @@
verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
verify(mPagedTileLayout).setListening(false, mUiEventLogger);
}
+
+
+ @Test
+ public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ when(mMediaHost.getVisible()).thenReturn(true);
+
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+
+ when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 4381158..0dfebab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.DisappearParameters;
@@ -93,6 +94,8 @@
QSTileView mQSTileView;
@Mock
PagedTileLayout mPagedTileLayout;
+ @Mock
+ FeatureFlags mFeatureFlags;
private QSPanelController mController;
@@ -118,7 +121,9 @@
mQSTileHost, mQSCustomizerController, true, mMediaHost,
mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
- /* labelsFlag */ false);
+ /* labelsFlag */ false,
+ mFeatureFlags
+ );
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 107160f..5870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -63,6 +64,8 @@
private lateinit var tileLayout: TileLayout
@Mock
private lateinit var tileView: QSTileView
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
private lateinit var controller: QuickQSPanelController
@@ -84,7 +87,8 @@
uiEventLogger,
qsLogger,
dumpManager,
- false
+ false,
+ featureFlags
)
controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index b452d3a..1ec1da4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -98,7 +98,7 @@
mQSCarrierGroupController = new QSCarrierGroupController.Builder(
mActivityStarter, handler, TestableLooper.get(this).getLooper(),
- mNetworkController, mCarrierTextControllerBuilder)
+ mNetworkController, mCarrierTextControllerBuilder, mContext)
.setQSCarrierGroup(mQSCarrierGroup)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
new file mode 100644
index 0000000..b7b9678
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.tiles
+
+import android.os.Handler
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.GlobalSettings
+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.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DeviceControlsTileTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var qsHost: QSHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ private lateinit var controlsComponent: ControlsComponent
+ @Mock
+ private lateinit var controlsUiController: ControlsUiController
+ @Mock
+ private lateinit var controlsListingController: ControlsListingController
+ @Mock
+ private lateinit var controlsController: ControlsController
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var controlsDialog: ControlsDialog
+ private lateinit var globalSettings: GlobalSettings
+ @Mock
+ private lateinit var serviceInfo: ControlsServiceInfo
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+ @Captor
+ private lateinit var listingCallbackCaptor:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var tile: DeviceControlsTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ `when`(qsHost.context).thenReturn(mContext)
+ `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+ controlsComponent = ControlsComponent(
+ true,
+ { controlsController },
+ { controlsUiController },
+ { controlsListingController }
+ )
+
+ globalSettings = FakeSettings()
+
+ globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
+ `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
+
+ tile = createTile()
+ }
+
+ @Test
+ fun testAvailable() {
+ assertThat(tile.isAvailable).isTrue()
+ }
+
+ @Test
+ fun testNotAvailableFeature() {
+ `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
+
+ assertThat(tile.isAvailable).isFalse()
+ }
+
+ @Test
+ fun testNotAvailableControls() {
+ controlsComponent = ControlsComponent(
+ false,
+ { controlsController },
+ { controlsUiController },
+ { controlsListingController }
+ )
+ tile = createTile()
+
+ assertThat(tile.isAvailable).isFalse()
+ }
+
+ @Test
+ fun testNotAvailableFlag() {
+ globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
+ tile = createTile()
+
+ assertThat(tile.isAvailable).isFalse()
+ }
+
+ @Test
+ fun testObservingCallback() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ any(ControlsListingController.ControlsListingCallback::class.java)
+ )
+ }
+
+ @Test
+ fun testLongClickIntent() {
+ assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+ }
+
+ @Test
+ fun testUnavailableByDefault() {
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testStateUnavailableIfNoListings() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(emptyList())
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testStateAvailableIfListings() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ }
+
+ @Test
+ fun testMoveBetweenStates() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ listingCallbackCaptor.value.onServicesUpdated(emptyList())
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ }
+
+ @Test
+ fun testNoDialogWhenUnavailable() {
+ tile.click()
+ testableLooper.processAllMessages()
+
+ verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+ }
+
+ @Test
+ fun testDialogShowWhenAvailable() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ tile.click()
+ testableLooper.processAllMessages()
+
+ verify(controlsDialog).show(controlsUiController)
+ }
+
+ @Test
+ fun testDialogDismissedOnDestroy() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ tile.click()
+ testableLooper.processAllMessages()
+
+ tile.destroy()
+ testableLooper.processAllMessages()
+ verify(controlsDialog).dismiss()
+ }
+
+ private fun createTile(): DeviceControlsTile {
+ return DeviceControlsTile(
+ qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ controlsComponent,
+ featureFlags,
+ { controlsDialog },
+ globalSettings
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index c1c6371..580f800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -57,6 +57,8 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ScrollCaptureClientTest extends SysuiTestCase {
+ private static final float MAX_PAGES = 3f;
+
private Context mContext;
private IWindowManager mWm;
@@ -96,7 +98,7 @@
Connection conn = mConnectionConsumer.getValue();
- conn.start(mSessionConsumer);
+ conn.start(mSessionConsumer, MAX_PAGES);
verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
Session session = mSessionConsumer.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
index bd37259..4c84df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
@@ -34,7 +34,7 @@
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView text = new TextView(this);
text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40);
- text.setText(com.android.systemui.R.string.test_content);
+ text.setText(com.android.systemui.tests.R.string.test_content);
linearLayout.addView(text);
scrollView.addView(linearLayout);
setContentView(scrollView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index d131dce..e16d4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -36,16 +36,20 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.os.Handler;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.util.Pair;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.DeviceConfigProxyFake;
import junit.framework.Assert;
@@ -55,38 +59,44 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class AssistantFeedbackControllerTest extends SysuiTestCase {
- private static final int ON = 1;
- private static final int OFF = 0;
private static final String TEST_PACKAGE_NAME = "test_package";
private static final int TEST_UID = 1;
private AssistantFeedbackController mAssistantFeedbackController;
+ private DeviceConfigProxyFake mProxyFake;
+ private TestableLooper mTestableLooper;
+
private StatusBarNotification mSbn;
@Before
public void setUp() {
- mAssistantFeedbackController = new AssistantFeedbackController(mContext);
- switchSetting(ON);
+ mProxyFake = new DeviceConfigProxyFake();
+ mTestableLooper = TestableLooper.get(this);
+ mAssistantFeedbackController = new AssistantFeedbackController(
+ new Handler(mTestableLooper.getLooper()),
+ mContext, mProxyFake);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
0, null, TEST_UID, 0, new Notification(),
UserHandle.CURRENT, null, 0);
}
@Test
- public void testUserControls_settingDisabled() {
- switchSetting(OFF);
+ public void testFlagDisabled() {
+ switchFlag("false");
assertFalse(mAssistantFeedbackController.isFeedbackEnabled());
}
@Test
- public void testUserControls_settingEnabled() {
+ public void testFlagEnabled() {
+ switchFlag("true");
assertTrue(mAssistantFeedbackController.isFeedbackEnabled());
}
@Test
- public void testFeedback_settingDisabled() {
- switchSetting(OFF);
+ public void testFeedback_flagDisabled() {
+ switchFlag("false");
assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus(
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
assertFalse(mAssistantFeedbackController.showFeedbackIndicator(
@@ -95,6 +105,7 @@
@Test
public void testFeedback_changedImportance() {
+ switchFlag("true");
NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED);
assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
assertTrue(mAssistantFeedbackController.showFeedbackIndicator(entry));
@@ -110,6 +121,7 @@
@Test
public void testFeedback_changedRanking() {
+ switchFlag("true");
NotificationEntry entry =
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED);
assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
@@ -121,8 +133,8 @@
}
@Test
- public void testGetFeedbackResources_settingDisabled() {
- switchSetting(OFF);
+ public void testGetFeedbackResources_flagDisabled() {
+ switchFlag("false");
Assert.assertEquals(new Pair(0, 0), mAssistantFeedbackController.getFeedbackResources(
getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
}
@@ -138,9 +150,10 @@
.build();
}
- private void switchSetting(int setting) {
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting);
- mAssistantFeedbackController.update(null);
+ private void switchFlag(String enabled) {
+ mProxyFake.setProperty(
+ DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK,
+ enabled, false);
+ mTestableLooper.processAllMessages();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6b0a23f..2e2945e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,7 +50,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -61,6 +60,7 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
+import com.android.systemui.tests.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 6fcc7fa..64a7bee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -43,7 +43,6 @@
import android.view.LayoutInflater;
import android.widget.RemoteViews;
-import com.android.systemui.R;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.media.MediaFeatureFlag;
@@ -68,6 +67,7 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.tests.R;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
import com.android.wm.shell.bubbles.Bubbles;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index a147c8d..45f7c5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.tests.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index b9fd75e..fa253e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -35,7 +35,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
@@ -58,7 +57,6 @@
@Mock private PowerManager mPowerManager;
@Mock private TunerService mTunerService;
@Mock private BatteryController mBatteryController;
- @Mock private FeatureFlags mFeatureFlags;
@Before
public void setup() {
@@ -69,8 +67,7 @@
mAlwaysOnDisplayPolicy,
mPowerManager,
mBatteryController,
- mTunerService,
- mFeatureFlags
+ mTunerService
);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ee1d758..4162884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -393,10 +393,11 @@
private void positionClock() {
mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
- mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
- mQsExpansion, mCutoutTopInset);
+ mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+ 0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
+ mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+ 0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
+ mCutoutTopInset);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c07ba72..d0e7031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -37,6 +37,7 @@
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
import android.os.PowerManager;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
@@ -58,6 +59,7 @@
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -68,7 +70,6 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -193,6 +194,8 @@
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
+ private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+ @Mock
private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock
private KeyguardClockSwitchController mKeyguardClockSwitchController;
@@ -216,6 +219,9 @@
private NotificationsQuickSettingsContainer mNotificationContainerParent;
@Mock
private AmbientState mAmbientState;
+ @Mock
+ private UserManager mUserManager;
+
private NotificationPanelViewController mNotificationPanelViewController;
private View.AccessibilityDelegate mAccessibiltyDelegate;
@@ -299,11 +305,12 @@
mBiometricUnlockController, mStatusBarKeyguardViewManager,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
+ mKeyguardUserSwitcherComponent,
mGroupManager,
mNotificationAreaController,
mAuthController,
- new QSDetailDisplayer(),
mScrimController,
+ mUserManager,
mMediaDataManager,
mAmbientState,
mFeatureFlags,
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 253460d..cae488a 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
@@ -97,7 +97,6 @@
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -259,7 +258,6 @@
@Mock private DemoModeController mDemoModeController;
@Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
@Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
- @Mock private FeatureFlags mFeatureFlags;
private ShadeController mShadeController;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private InitController mInitController = new InitController();
@@ -420,8 +418,7 @@
mNotificationShadeDepthControllerLazy,
mStatusBarTouchableRegionManager,
mNotificationIconAreaController,
- mBrightnessSliderFactory,
- mFeatureFlags);
+ mBrightnessSliderFactory);
when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
mLockIconContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index c212cf3..67c1a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -26,12 +26,12 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.settingslib.R;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tests.R;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index fc1a791..e479882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -60,9 +60,9 @@
@Mock
private lateinit var layoutInflater: LayoutInflater
@Mock
- private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+ private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
- private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+ private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
private lateinit var picture: Bitmap
@Before
@@ -72,8 +72,11 @@
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
`when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(inflatedUserDetailItemView)
- adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
- keyguardUserSwitcher)
+ adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
+ mContext,
+ mContext.resources,
+ LayoutInflater.from(mContext),
+ userSwitcherController, keyguardUserSwitcherController)
picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
@@ -118,11 +121,11 @@
}
@Test
- fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+ fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
val v: UserDetailItemView? = createViewFromSameType(
isCurrentUser = true, isGuestUser = false)
assertNotNull(v)
- verify(v)!!.setOnClickListener(null)
+ verify(v)!!.setOnClickListener(adapter)
}
@Test
@@ -150,11 +153,11 @@
}
@Test
- fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+ fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
val v: UserDetailItemView? = createViewFromDifferentType(
isCurrentUser = true, isGuestUser = false)
assertNotNull(v)
- verify(v)!!.setOnClickListener(null)
+ verify(v)!!.setOnClickListener(adapter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index f8b6383..89cc2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -430,6 +430,10 @@
updateSignalStrength();
}
+ public void setImsType(int imsType) {
+ mMobileSignalController.setImsType(imsType);
+ }
+
public void setIsGsm(boolean gsm) {
when(mSignalStrength.isGsm()).thenReturn(gsm);
updateSignalStrength();
@@ -632,6 +636,14 @@
}
}
+ protected void verifyLastCallStrength(int icon) {
+ ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setCallIndicator(
+ iconArg.capture(),
+ anyInt());
+ assertEquals("Call strength, in status bar", icon, (int) iconArg.getValue().icon);
+ }
+
protected void assertNetworkNameEquals(String expected) {
assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 10166cb..fc1a08ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -15,6 +15,7 @@
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.telephony.CellSignalStrength;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -243,6 +244,28 @@
}
}
+ @Test
+ public void testCallStrengh() {
+ String testSsid = "Test SSID";
+ setWifiEnabled(true);
+ setWifiState(true, testSsid);
+ // Set the ImsType to be IMS_TYPE_WLAN
+ setImsType(2);
+ setWifiLevel(1);
+ for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+ setWifiLevel(testLevel);
+ verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]);
+ }
+ // Set the ImsType to be IMS_TYPE_WWAN
+ setImsType(1);
+ for (int testStrength = 0;
+ testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
+ setupDefaultSignal();
+ setLevel(testStrength);
+ verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
+ }
+ }
+
protected void setWifiActivity(int activity) {
// TODO: Not this, because this variable probably isn't sticking around.
mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index edaff5f..45828c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -32,13 +32,18 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -70,11 +75,12 @@
private static final String TEST_DISABLED_PREFIX = "com.example.";
private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
- private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap();
+ private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap();
static {
for (String category : THEME_CATEGORIES) {
- ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category);
+ ALL_CATEGORIES_MAP.put(category,
+ new OverlayIdentifier(TEST_DISABLED_PREFIX + category));
}
}
@@ -87,6 +93,8 @@
OverlayManager mOverlayManager;
@Mock
DumpManager mDumpManager;
+ @Mock
+ OverlayManagerTransaction.Builder mTransactionBuilder;
private ThemeOverlayApplier mManager;
@@ -94,7 +102,12 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
- LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
+ LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+ @Override
+ protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+ return mTransactionBuilder;
+ }
+ };
when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
.thenReturn(Lists.newArrayList(
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
@@ -147,24 +160,26 @@
@Test
public void allCategoriesSpecified_allEnabledExclusively() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
+ verify(mOverlayManager).commit(any());
- for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
}
@Test
public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
- for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
+ for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true),
+ eq(UserHandle.SYSTEM.getIdentifier()));
} else {
- verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
- entry.getValue(), UserHandle.SYSTEM);
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
}
}
}
@@ -174,17 +189,34 @@
Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
UserHandle newUserHandle = UserHandle.of(10);
userHandles.add(newUserHandle);
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles);
- for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+ for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(newUserHandle.getIdentifier()));
+ }
+ }
+
+ @Test
+ public void applyCurrentUserOverlays_createsPendingOverlays() {
+ Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
+ UserHandle newUserHandle = UserHandle.of(10);
+ userHandles.add(newUserHandle);
+ FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] {
+ mock(FabricatedOverlay.class)
+ };
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles);
+
+ for (FabricatedOverlay overlay : pendingCreation) {
+ verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
}
}
@Test
public void allCategoriesSpecified_overlayManagerNotQueried() {
- mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
verify(mOverlayManager, never())
.getOverlayInfosForTarget(anyString(), any(UserHandle.class));
@@ -192,48 +224,56 @@
@Test
public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
- for (String overlayPackage : categoryToPackage.values()) {
- verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+ for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
+ verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS,
- false, TEST_USER);
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
- false, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
+ eq(false), eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)),
+ eq(false), eq(TEST_USER.getIdentifier()));
}
@Test
public void zeroCategoriesSpecified_allDisabled() {
- mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES);
for (String category : THEME_CATEGORIES) {
- verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER);
+ verify(mTransactionBuilder).setEnabled(
+ eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false),
+ eq(TEST_USER.getIdentifier()));
}
}
@Test
public void nonThemeCategorySpecified_ignored() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
- categoryToPackage.put("blah.category", "com.example.blah.category");
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
- verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER);
- verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category",
- TEST_USER);
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
+ eq(TEST_USER.getIdentifier()));
+ verify(mTransactionBuilder, never()).setEnabled(
+ eq(new OverlayIdentifier("com.example.blah.category")), eq(true),
+ eq(TEST_USER.getIdentifier()));
}
@Test
public void overlayManagerOnlyQueriedForUnspecifiedPackages() {
- Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+ Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
- mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+ mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM);
verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE,
@@ -247,7 +287,8 @@
private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName,
String category, boolean enabled) {
- return new OverlayInfo(packageName, targetPackageName, null, category, "",
- enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false);
+ return new OverlayInfo(packageName, null, targetPackageName, null, category, "",
+ enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false,
+ false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d33fac0..f7f8d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.theme;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
@@ -27,12 +25,15 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
import android.graphics.Color;
import android.os.Handler;
import android.os.UserHandle;
@@ -40,11 +41,13 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
@@ -56,7 +59,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -85,6 +87,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
@Captor
@@ -93,10 +97,20 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mFeatureFlags.isMonetEnabled()).thenReturn(true);
mThemeOverlayController = new ThemeOverlayController(null /* context */,
mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
- mDumpManager);
+ mDumpManager, mFeatureFlags) {
+ @Nullable
+ @Override
+ protected FabricatedOverlay getOverlay(int color, int type) {
+ FabricatedOverlay overlay = mock(FabricatedOverlay.class);
+ when(overlay.getIdentifier())
+ .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
+ return overlay;
+ }
+ };
mThemeOverlayController.start();
if (USE_LOCK_SCREEN_WALLPAPER) {
@@ -106,10 +120,6 @@
verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
eq(UserHandle.USER_ALL));
verify(mDumpManager).registerDumpable(any(), any());
-
- List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC);
- when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
- when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
}
@Test
@@ -128,17 +138,17 @@
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
- verify(mThemeOverlayApplier).getAvailableSystemColors();
- verify(mThemeOverlayApplier).getAvailableAccentColors();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000");
+ .isEqualTo(new OverlayIdentifier("ffff0000"));
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
- .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF");
+ .isEqualTo(new OverlayIdentifier("ff0000ff"));
// Should not ask again if changed to same value
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
@@ -146,49 +156,6 @@
}
@Test
- public void onWallpaperColorsChanged_whiteTheme() {
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE),
- Color.valueOf(Color.BLUE), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
- }
-
- @Test
- public void onWallpaperColorsChanged_blackTheme() {
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK),
- Color.valueOf(Color.BLUE), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
- }
-
- @Test
- public void onWallpaperColorsChanged_addsLeadingZerosToColors() {
- // Should ask for a new theme when wallpaper colors change
- WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC),
- Color.valueOf(0x000CCC), null);
- mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
- // Assert that we received the colors that we were expecting
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC");
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
- .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC");
- }
-
- @Test
public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -201,14 +168,37 @@
.thenReturn(jsonString);
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
- ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
- verify(mThemeOverlayApplier).getAvailableSystemColors();
- verify(mThemeOverlayApplier).getAvailableAccentColors();
- verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
- .isEqualTo("override.package.name");
+ .isEqualTo(new OverlayIdentifier("override.package.name"));
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() {
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+ ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+ ArgumentCaptor.forClass(Map.class);
+
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
+
+ // Assert that we received the colors that we were expecting
+ assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+ .isEqualTo(new OverlayIdentifier("ff00ff00"));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index c743fd0..365c62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -57,8 +57,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.FeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -88,7 +87,6 @@
private static final String TEXT = "Hello World";
private static final int MESSAGE_RES_ID = R.id.message;
- private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
private Context mContextSpy;
private ToastUI mToastUI;
@Mock private LayoutInflater mLayoutInflater;
@@ -99,6 +97,7 @@
@Mock private PluginManager mPluginManager;
@Mock private DumpManager mDumpManager;
@Mock private ToastLogger mToastLogger;
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private ITransientNotificationCallback mCallback;
@Captor private ArgumentCaptor<View> mViewCaptor;
@@ -107,12 +106,9 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- // This is because inflate will result in WindowManager (WM) calls, which will fail since we
- // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM.
- View view = ToastPresenter.getTextToastView(mContext, TEXT);
- when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view);
- mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater);
+ when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
+ ToastPresenter.getTextToastView(mContext, TEXT));
+ when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
mContextSpy = spy(mContext);
@@ -120,8 +116,8 @@
doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
- mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
- mFakeDelayableExecutor, mToastLogger);
+ mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
+ mDumpManager, mFeatureFlags), mToastLogger);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c0af15b..203ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -19,8 +19,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.List;
@@ -66,7 +66,7 @@
}
@Override
- public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+ public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
}
@Override
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 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
mBubblesManager = new BubblesManager(
mContext,
- mBubbleController.getImpl(),
+ mBubbleController.asBubbles(),
mNotificationShadeWindowController,
mStatusBarStateController,
mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index f4d96a12..ab329c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
@@ -20,7 +20,7 @@
import android.content.Intent;
import android.os.Bundle;
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
/**
* Referenced by NotificationTestHelper#makeBubbleMetadata
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 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
mBubblesManager = new BubblesManager(
mContext,
- mBubbleController.getImpl(),
+ mBubbleController.asBubbles(),
mNotificationShadeWindowController,
mStatusBarStateController,
mShadeController,
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 83a5036..afa0840 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,7 +81,6 @@
":dumpstate_aidl",
":framework_native_aidl",
":gsiservice_aidl",
- ":idmap2_aidl",
":inputconstants_aidl",
":installd_aidl",
":storaged_aidl",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 737a9e4..342208c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -998,6 +998,18 @@
public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
/**
+ * Register to listen for loading progress of an installed package.
+ * The listener is automatically unregistered when the app is fully loaded.
+ * @param packageName The name of the installed package
+ * @param callback To loading reporting progress
+ * @param userId The user under which to check.
+ * @return Whether the registration was successful. It can fail if the package has not been
+ * installed yet.
+ */
+ public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
+ @NonNull InstalledLoadingProgressCallback callback, int userId);
+
+ /**
* Returns the string representation of a known package. For example,
* {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
*
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 42b6a7f..277152d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -6407,20 +6407,18 @@
Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
}
- // Prioritize the user portal URL from the network agent.
- if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null
- || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) {
- captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl());
+ // Prioritize the user portal URL from the network agent if the source is authenticated.
+ if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource()
+ != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+ captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(),
+ apiData.getUserPortalUrlSource());
}
- // Prioritize the venue information URL from the network agent.
- if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null
- || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) {
- captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl());
-
- // Note that venue friendly name can only come from the network agent because it is not
- // in use in RFC8908. However, if using the Capport venue URL, make sure that the
- // friendly name is not set from the network agent.
- captivePortalBuilder.setVenueFriendlyName(null);
+ // Prioritize the venue information URL from the network agent if the source is
+ // authenticated.
+ if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource()
+ != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+ captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(),
+ apiData.getVenueInfoUrlSource());
}
return captivePortalBuilder.build();
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index f648c3e..b48bc90 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -29,6 +29,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
import android.net.IIpSecService;
import android.net.INetd;
import android.net.InetAddresses;
@@ -41,6 +42,7 @@
import android.net.IpSecTunnelInterfaceResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.TrafficStats;
import android.net.util.NetdService;
@@ -797,9 +799,15 @@
}
}
- private final class TunnelInterfaceRecord extends OwnedResourceRecord {
+ /**
+ * Tracks an tunnel interface, and manages cleanup paths.
+ *
+ * <p>This class is not thread-safe, and expects that that users of this class will ensure
+ * synchronization and thread safety by holding the IpSecService.this instance lock
+ */
+ @VisibleForTesting
+ final class TunnelInterfaceRecord extends OwnedResourceRecord {
private final String mInterfaceName;
- private final Network mUnderlyingNetwork;
// outer addresses
private final String mLocalAddress;
@@ -810,6 +818,8 @@
private final int mIfId;
+ private Network mUnderlyingNetwork;
+
TunnelInterfaceRecord(
int resourceId,
String interfaceName,
@@ -870,14 +880,22 @@
releaseNetId(mOkey);
}
- public String getInterfaceName() {
- return mInterfaceName;
+ @GuardedBy("IpSecService.this")
+ public void setUnderlyingNetwork(Network underlyingNetwork) {
+ // When #applyTunnelModeTransform is called, this new underlying network will be used to
+ // update the output mark of the input transform.
+ mUnderlyingNetwork = underlyingNetwork;
}
+ @GuardedBy("IpSecService.this")
public Network getUnderlyingNetwork() {
return mUnderlyingNetwork;
}
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
/** Returns the local, outer address for the tunnelInterface */
public String getLocalAddress() {
return mLocalAddress;
@@ -1429,6 +1447,34 @@
}
}
+ /** Set TunnelInterface to use a specific underlying network. */
+ @Override
+ public synchronized void setNetworkForTunnelInterface(
+ int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+ enforceTunnelFeatureAndPermissions(callingPackage);
+ Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+ final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+ // Get tunnelInterface record; if no such interface is found, will throw
+ // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+ final ConnectivityManager connectivityManager =
+ mContext.getSystemService(ConnectivityManager.class);
+ final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+ throw new IllegalArgumentException(
+ "Underlying network cannot be the network being exposed by this tunnel");
+ }
+
+ // It is meaningless to check if the network exists or is valid because the network might
+ // disconnect at any time after it passes the check.
+
+ tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+ }
+
/**
* Delete a TunnelInterface that has been been allocated by and registered with the system
* server
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a6cfae4..c6a8660 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -2,7 +2,7 @@
per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
# Vibrator / Threads
-per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
import android.os.UserManager;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.R;
@@ -147,14 +148,15 @@
private int getAllowedUid(int userHandle) {
String allowedPackage = mContext.getResources()
.getString(R.string.config_persistentDataPackageName);
- PackageManager pm = mContext.getPackageManager();
int allowedUid = -1;
- try {
- allowedUid = pm.getPackageUidAsUser(allowedPackage,
- PackageManager.MATCH_SYSTEM_ONLY, userHandle);
- } catch (PackageManager.NameNotFoundException e) {
- // not expected
- Slog.e(TAG, "not able to find package " + allowedPackage, e);
+ if (!TextUtils.isEmpty(allowedPackage)) {
+ try {
+ allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+ allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+ } catch (PackageManager.NameNotFoundException e) {
+ // not expected
+ Slog.e(TAG, "not able to find package " + allowedPackage, e);
+ }
}
return allowedUid;
}
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index e7e5d67..d264f85 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -18,9 +18,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
@@ -74,6 +79,7 @@
/** System implementation of {@link IVibratorManagerService}. */
public class VibratorManagerService extends IVibratorManagerService.Stub {
private static final String TAG = "VibratorManagerService";
+ private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final boolean DEBUG = false;
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
@@ -89,7 +95,7 @@
@Override
public void onStart() {
mService = new VibratorManagerService(getContext(), new Injector());
- publishBinderService("vibrator_manager", mService);
+ publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
}
@Override
@@ -105,6 +111,7 @@
private final Object mLock = new Object();
private final Context mContext;
+ private final String mSystemUiPackage;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final Handler mHandler;
@@ -128,6 +135,26 @@
private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate;
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ synchronized (mLock) {
+ // When the system is entering a non-interactive state, we want
+ // to cancel vibrations in case a misbehaving app has abandoned
+ // them. However it may happen that the system is currently playing
+ // haptic feedback as part of the transition. So we don't cancel
+ // system vibrations.
+ if (mCurrentVibration != null
+ && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
+ mNextVibration = null;
+ mCurrentVibration.cancel();
+ }
+ }
+ }
+ }
+ };
+
static native long nativeInit(OnSyncedVibrationCompleteListener listener);
static native long nativeGetFinalizer();
@@ -155,6 +182,9 @@
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+ mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+ .getSystemUiServiceComponent().getPackageName();
+
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
@@ -184,6 +214,12 @@
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).off();
}
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ context.registerReceiver(mIntentReceiver, filter);
+
+ injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
}
/** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -371,7 +407,7 @@
mVibratorManagerRecords.record(mCurrentExternalVibration);
mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null;
- // TODO(b/167946816): set external control to false
+ setExternalControl(false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -432,6 +468,12 @@
}
}
+ private void setExternalControl(boolean externalControl) {
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mVibrators.valueAt(i).setExternalControl(externalControl);
+ }
+ }
+
@GuardedBy("mLock")
private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
for (int i = 0; i < vib.effects.size(); i++) {
@@ -507,6 +549,12 @@
}
@GuardedBy("mLock")
+ private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
+ vib.end(status);
+ mVibratorManagerRecords.record(vib);
+ }
+
+ @GuardedBy("mLock")
private void reportFinishedVibrationLocked(Vibration.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -827,6 +875,13 @@
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean isSystemHapticFeedback(Vibration vib) {
+ if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
+ return false;
+ }
+ return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
+ }
+
@GuardedBy("mLock")
private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
for (int i = 0; i < mVibrators.size(); i++) {
@@ -859,6 +914,10 @@
VibratorController.OnVibrationCompleteListener listener) {
return new VibratorController(vibratorId, listener);
}
+
+ void addService(String name, IBinder service) {
+ ServiceManager.addService(name, service);
+ }
}
/**
@@ -1088,14 +1147,14 @@
}
pw.println();
pw.println(" mCurrentVibration:");
- pw.println(" " + mCurrentVibration == null
- ? null : mCurrentVibration.getVibration().getDebugInfo());
+ pw.println(" " + (mCurrentVibration == null
+ ? null : mCurrentVibration.getVibration().getDebugInfo()));
pw.println(" mNextVibration:");
- pw.println(" " + mNextVibration == null
- ? null : mNextVibration.getVibration().getDebugInfo());
+ pw.println(" " + (mNextVibration == null
+ ? null : mNextVibration.getVibration().getDebugInfo()));
pw.println(" mCurrentExternalVibration:");
- pw.println(" " + mCurrentExternalVibration == null
- ? null : mCurrentExternalVibration.getDebugInfo());
+ pw.println(" " + (mCurrentExternalVibration == null
+ ? null : mCurrentExternalVibration.getDebugInfo()));
pw.println();
pw.println(" mVibrationSettings=" + mVibrationSettings);
for (int i = 0; i < mPreviousVibrations.size(); i++) {
@@ -1168,6 +1227,145 @@
}
}
+ /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
+ private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+ ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
+
+ @Override
+ public int onExternalVibrationStart(ExternalVibration vib) {
+ if (!hasExternalControlCapability()) {
+ return IExternalVibratorService.SCALE_MUTE;
+ }
+ if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+ vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ + " tried to play externally controlled vibration"
+ + " without VIBRATE permission, ignoring.");
+ return IExternalVibratorService.SCALE_MUTE;
+ }
+
+ int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
+ vib.getVibrationAttributes());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+ endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
+ } else {
+ endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
+ }
+ return vibHolder.scale;
+ }
+
+ VibrationThread cancelingVibration = null;
+ int scale;
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ // We are already playing this external vibration, so we can return the same
+ // scale calculated in the previous call to this method.
+ return mCurrentExternalVibration.scale;
+ }
+ if (mCurrentExternalVibration == null) {
+ // If we're not under external control right now, then cancel any normal
+ // vibration that may be playing and ready the vibrator for external control.
+ if (mCurrentVibration != null) {
+ mNextVibration = null;
+ mCurrentVibration.cancel();
+ cancelingVibration = mCurrentVibration;
+ }
+ } else {
+ endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+ }
+ // At this point we either have an externally controlled vibration playing, or
+ // no vibration playing. Since the interface defines that only one externally
+ // controlled vibration can play at a time, by returning something other than
+ // SCALE_MUTE from this function we can be assured that if we are currently
+ // playing vibration, it will be muted in favor of the new vibration.
+ //
+ // Note that this doesn't support multiple concurrent external controls, as we
+ // would need to mute the old one still if it came from a different controller.
+ mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+ mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+ vib.linkToDeath(mCurrentExternalDeathRecipient);
+ mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
+ vib.getVibrationAttributes().getUsage());
+ scale = mCurrentExternalVibration.scale;
+ }
+
+ if (cancelingVibration != null) {
+ try {
+ cancelingVibration.join();
+ } catch (InterruptedException e) {
+ Slog.w("Interrupted while waiting for vibration to finish before starting "
+ + "external control", e);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator going under external control.");
+ }
+ setExternalControl(true);
+ if (DEBUG) {
+ Slog.e(TAG, "Playing external vibration: " + vib);
+ }
+ return scale;
+ }
+
+ @Override
+ public void onExternalVibrationStop(ExternalVibration vib) {
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ if (DEBUG) {
+ Slog.e(TAG, "Stopping external vibration" + vib);
+ }
+ stopExternalVibrateLocked(Vibration.Status.FINISHED);
+ }
+ }
+ }
+
+ private void stopExternalVibrateLocked(Vibration.Status status) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
+ try {
+ if (mCurrentExternalVibration == null) {
+ return;
+ }
+ endVibrationLocked(mCurrentExternalVibration, status);
+ mCurrentExternalVibration.externalVibration.unlinkToDeath(
+ mCurrentExternalDeathRecipient);
+ mCurrentExternalDeathRecipient = null;
+ mCurrentExternalVibration = null;
+ setExternalControl(false);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private boolean hasExternalControlCapability() {
+ for (int i = 0; i < mVibrators.size(); i++) {
+ if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ synchronized (mLock) {
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "External vibration finished because binder died");
+ }
+ stopExternalVibrateLocked(Vibration.Status.CANCELLED);
+ }
+ }
+ }
+ }
+ }
+
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
private final class VibratorManagerShellCommand extends ShellCommand {
public static final String SHELL_PACKAGE_NAME = "com.android.shell";
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
deleted file mode 100644
index 2ac365d..0000000
--- a/services/core/java/com/android/server/VibratorService.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package com.android.server;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.CombinedVibrationEffect;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/** System implementation of {@link IVibratorService}. */
-public class VibratorService extends IVibratorService.Stub {
- private static final String TAG = "VibratorService";
- private static final boolean DEBUG = false;
- private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-
- // Default vibration attributes. Used when vibration is requested without attributes
- private static final VibrationAttributes DEFAULT_ATTRIBUTES =
- new VibrationAttributes.Builder().build();
-
- // Used to generate globally unique vibration ids.
- private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
- private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations;
- private final LinkedList<Vibration.DebugInfo> mPreviousVibrations;
- private final int mPreviousVibrationsLimit;
- private final Handler mH;
- private final Object mLock = new Object();
- private final VibratorController mVibratorController;
- private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
-
- private final Context mContext;
- private final PowerManager.WakeLock mWakeLock;
- private final AppOpsManager mAppOps;
- private final IBatteryStats mBatteryStatsService;
- private final String mSystemUiPackage;
- private VibrationSettings mVibrationSettings;
- private VibrationScaler mVibrationScaler;
- private InputDeviceDelegate mInputDeviceDelegate;
-
- @GuardedBy("mLock")
- private VibrationThread mThread;
- @GuardedBy("mLock")
- private VibrationThread mNextVibrationThread;
-
- @GuardedBy("mLock")
- private Vibration mCurrentVibration;
- private int mCurVibUid = -1;
- private ExternalVibrationHolder mCurrentExternalVibration;
-
- /**
- * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished
- * vibrations.
- */
- private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
-
- @Override
- public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
- return false;
- }
-
- @Override
- public boolean triggerSyncedVibration(long vibrationId) {
- return false;
- }
-
- @Override
- public void cancelSyncedVibration() {
- }
-
- @Override
- public void onVibrationEnded(long vibrationId, Vibration.Status status) {
- if (DEBUG) {
- Slog.d(TAG, "Vibration thread finished with status " + status);
- }
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
- mThread = null;
- reportFinishVibrationLocked(status);
- if (mNextVibrationThread != null) {
- startVibrationThreadLocked(mNextVibrationThread);
- mNextVibrationThread = null;
- }
- }
- }
- }
- }
-
- /**
- * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service.
- */
- private static final class VibrationCompleteListener implements OnVibrationCompleteListener {
- private WeakReference<VibratorService> mServiceRef;
-
- VibrationCompleteListener(VibratorService service) {
- mServiceRef = new WeakReference<>(service);
- }
-
- @Override
- public void onComplete(int vibratorId, long vibrationId) {
- VibratorService service = mServiceRef.get();
- if (service != null) {
- service.onVibrationComplete(vibratorId, vibrationId);
- }
- }
- }
-
- /** Holder for a {@link ExternalVibration}. */
- private final class ExternalVibrationHolder {
-
- public final ExternalVibration externalVibration;
- public int scale;
-
- private final long mStartTimeDebug;
- private long mEndTimeDebug;
- private Vibration.Status mStatus;
-
- private ExternalVibrationHolder(ExternalVibration externalVibration) {
- this.externalVibration = externalVibration;
- this.scale = IExternalVibratorService.SCALE_NONE;
- mStartTimeDebug = System.currentTimeMillis();
- mStatus = Vibration.Status.RUNNING;
- }
-
- public void end(Vibration.Status status) {
- if (mStatus != Vibration.Status.RUNNING) {
- // Vibration already ended, keep first ending status set and ignore this one.
- return;
- }
- mStatus = status;
- mEndTimeDebug = System.currentTimeMillis();
- }
-
- public Vibration.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
- scale, externalVibration.getVibrationAttributes(),
- externalVibration.getUid(), externalVibration.getPackage(),
- /* reason= */ null, mStatus);
- }
- }
-
- VibratorService(Context context) {
- this(context, new Injector());
- }
-
- @VisibleForTesting
- VibratorService(Context context, Injector injector) {
- mH = injector.createHandler(Looper.myLooper());
- mVibratorController = injector.createVibratorController(
- new VibrationCompleteListener(this));
-
- // Reset the hardware to a default state, in case this is a runtime
- // restart instead of a fresh boot.
- mVibratorController.off();
-
- mContext = context;
- PowerManager pm = context.getSystemService(PowerManager.class);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
- mWakeLock.setReferenceCounted(true);
-
- mAppOps = mContext.getSystemService(AppOpsManager.class);
- mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
- BatteryStats.SERVICE_NAME));
- mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
- .getSystemUiServiceComponent().getPackageName();
-
- mPreviousVibrationsLimit = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-
- mPreviousRingVibrations = new LinkedList<>();
- mPreviousNotificationVibrations = new LinkedList<>();
- mPreviousAlarmVibrations = new LinkedList<>();
- mPreviousVibrations = new LinkedList<>();
- mPreviousExternalVibrations = new LinkedList<>();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- context.registerReceiver(mIntentReceiver, filter);
-
- injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
- }
-
- public void systemReady() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
- try {
- mVibrationSettings = new VibrationSettings(mContext, mH);
- mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
- mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
-
- mVibrationSettings.addListener(this::updateVibrators);
-
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mVibrationSettings.updateSettings();
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
-
- updateVibrators();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- /** Callback for when vibration is complete, to be called by native. */
- @VisibleForTesting
- public void onVibrationComplete(int vibratorId, long vibrationId) {
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
- && mThread != null) {
- if (DEBUG) {
- Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
- }
- // Let the thread playing the vibration handle the callback, since it might be
- // expecting the vibrator to turn off multiple times during a single vibration.
- mThread.vibratorComplete(vibratorId);
- }
- }
- }
-
- @Override // Binder call
- public boolean hasVibrator() {
- // For now, we choose to ignore the presence of input devices that have vibrators
- // when reporting whether the device has a vibrator. Applications often use this
- // information to decide whether to enable certain features so they expect the
- // result of hasVibrator() to be constant. For now, just report whether
- // the device has a built-in vibrator.
- return mVibratorController.isAvailable();
- }
-
- @Override // Binder call
- public boolean isVibrating() {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.isVibrating();
- }
-
- @Override // Binder call
- public VibratorInfo getVibratorInfo() {
- return mVibratorController.getVibratorInfo();
- }
-
- @Override // Binder call
- public boolean registerVibratorStateListener(IVibratorStateListener listener) {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.registerVibratorStateListener(listener);
- }
-
- @Override // Binder call
- @GuardedBy("mLock")
- public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
- if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
- throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
- }
- return mVibratorController.unregisterVibratorStateListener(listener);
- }
-
- @Override // Binder call
- public boolean hasAmplitudeControl() {
- // Input device vibrators always support amplitude controls.
- return mInputDeviceDelegate.isAvailable()
- || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
- }
-
- private void verifyIncomingUid(int uid) {
- if (uid == Binder.getCallingUid()) {
- return;
- }
- if (Binder.getCallingPid() == Process.myPid()) {
- return;
- }
- mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
-
- /**
- * Validate the incoming VibrationEffect.
- *
- * We can't throw exceptions here since we might be called from some system_server component,
- * which would bring the whole system down.
- *
- * @return whether the VibrationEffect is valid
- */
- private static boolean verifyVibrationEffect(VibrationEffect effect) {
- if (effect == null) {
- // Effect must not be null.
- Slog.wtf(TAG, "effect must not be null");
- return false;
- }
- try {
- effect.validate();
- } catch (Exception e) {
- Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
- return false;
- }
- return true;
- }
-
- private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked
- && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
- return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
- fallback);
- }
- return effect;
- }
-
- private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
- if (attrs == null) {
- attrs = DEFAULT_ATTRIBUTES;
- }
- if (shouldBypassDnd(attrs)) {
- if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getFlags()
- & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new VibrationAttributes.Builder(attrs)
- .setFlags(flags, attrs.getFlags()).build();
- }
- }
-
- return attrs;
- }
-
- @Override // Binder call
- public void vibrate(int uid, String opPkg, VibrationEffect effect,
- @Nullable VibrationAttributes attrs, String reason, IBinder token) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
- try {
- if (!hasPermission(android.Manifest.permission.VIBRATE)) {
- throw new SecurityException("Requires VIBRATE permission");
- }
- if (token == null) {
- Slog.e(TAG, "token must not be null");
- return;
- }
- verifyIncomingUid(uid);
- if (!verifyVibrationEffect(effect)) {
- return;
- }
- effect = fixupVibrationEffect(effect);
- attrs = fixupVibrationAttributes(attrs);
- Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(),
- CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason);
-
- // If our current vibration is longer than the new vibration and is the same amplitude,
- // then just let the current one finish.
- synchronized (mLock) {
- VibrationEffect currentEffect =
- mCurrentVibration == null ? null : getEffect(mCurrentVibration);
- if (effect instanceof VibrationEffect.OneShot
- && currentEffect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
- VibrationEffect.OneShot currentOneShot =
- (VibrationEffect.OneShot) currentEffect;
- if (currentOneShot.getDuration() > newOneShot.getDuration()
- && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
- if (DEBUG) {
- Slog.d(TAG,
- "Ignoring incoming vibration in favor of current vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING);
- return;
- }
- }
-
-
- // If something has external control of the vibrator, assume that it's more
- // important for now.
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL);
- return;
- }
-
- // If the current vibration is repeating and the incoming one is non-repeating,
- // then ignore the non-repeating vibration. This is so that we don't cancel
- // vibrations that are meant to grab the attention of the user, like ringtones and
- // alarms, in favor of one-shot vibrations that are likely quite short.
- if (!isRepeatingVibration(effect)
- && mCurrentVibration != null
- && isRepeatingVibration(currentEffect)) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM);
- return;
- }
-
- if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) {
- Slog.e(TAG, "Ignoring incoming vibration as process with"
- + " uid= " + uid + " is background,"
- + " attrs= " + vib.attrs);
- endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND);
- return;
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- startVibrationLocked(vib);
- boolean isNextVibration = mNextVibrationThread != null
- && vib.equals(mNextVibrationThread.getVibration());
-
- if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
- // Vibration was unexpectedly ignored: add to list for debugging
- endVibrationLocked(vib, Vibration.Status.IGNORED);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private boolean hasPermission(String permission) {
- return mContext.checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private static boolean isRepeatingVibration(VibrationEffect effect) {
- return effect.getDuration() == Long.MAX_VALUE;
- }
-
- private static <T extends VibrationEffect> T getEffect(Vibration vib) {
- return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect();
- }
-
- private void endVibrationLocked(Vibration vib, Vibration.Status status) {
- final LinkedList<Vibration.DebugInfo> previousVibrations;
- switch (vib.attrs.getUsage()) {
- case VibrationAttributes.USAGE_NOTIFICATION:
- previousVibrations = mPreviousNotificationVibrations;
- break;
- case VibrationAttributes.USAGE_RINGTONE:
- previousVibrations = mPreviousRingVibrations;
- break;
- case VibrationAttributes.USAGE_ALARM:
- previousVibrations = mPreviousAlarmVibrations;
- break;
- default:
- previousVibrations = mPreviousVibrations;
- }
- if (previousVibrations.size() > mPreviousVibrationsLimit) {
- previousVibrations.removeFirst();
- }
- vib.end(status);
- previousVibrations.addLast(vib.getDebugInfo());
- }
-
- private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
- if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
- mPreviousExternalVibrations.removeFirst();
- }
- vib.end(status);
- mPreviousExternalVibrations.addLast(vib.getDebugInfo());
- }
-
- @Override // Binder call
- public void cancelVibrate(IBinder token) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.VIBRATE,
- "cancelVibrate");
-
- synchronized (mLock) {
- if (mCurrentVibration != null && mCurrentVibration.token == token) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
- }
-
- @GuardedBy("mLock")
- private void doCancelVibrateLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
- try {
- if (mThread != null) {
- mThread.cancel();
- }
- mInputDeviceDelegate.cancelVibrateIfAvailable();
- if (mCurrentExternalVibration != null) {
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.mute();
- mCurrentExternalVibration = null;
- mVibratorController.setExternalControl(false);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationLocked(final Vibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
- try {
- if (!shouldVibrate(vib)) {
- return;
- }
- applyVibrationIntensityScalingLocked(vib);
- startVibrationInnerLocked(vib);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationInnerLocked(Vibration vib) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
- try {
- boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
- vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
- if (inputDevicesAvailable) {
- endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
- } else if (mThread == null) {
- startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
- mBatteryStatsService, mVibrationCallbacks));
- } else {
- mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
- mBatteryStatsService, mVibrationCallbacks);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @GuardedBy("mLock")
- private void startVibrationThreadLocked(VibrationThread thread) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration = thread.getVibration();
- mThread = thread;
- mThread.start();
- }
-
- /** Scale the vibration effect by the intensity as appropriate based its intent. */
- private void applyVibrationIntensityScalingLocked(Vibration vib) {
- vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
- }
-
- private static boolean shouldBypassDnd(VibrationAttributes attrs) {
- return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
- }
-
- private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
- int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
- attrs.getAudioUsage(), uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
- }
-
- if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
- // If we're just ignoring the vibration op then this is set by DND and we should ignore
- // if we're asked to bypass. AppOps won't be able to record this operation, so make
- // sure we at least note it in the logs for debugging.
- Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
- mode = AppOpsManager.MODE_ALLOWED;
- }
- return mode;
- }
-
- private boolean shouldVibrate(Vibration vib) {
- if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) {
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER);
- return false;
- }
-
- int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage());
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS);
- return false;
- }
-
- if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE);
- return false;
- }
-
- final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (mode == AppOpsManager.MODE_ERRORED) {
- // We might be getting calls from within system_server, so we don't actually
- // want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
- endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS);
- }
- return false;
- }
-
- return true;
- }
-
- @GuardedBy("mLock")
- private void reportFinishVibrationLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
- try {
- if (mCurrentVibration != null) {
- endVibrationLocked(mCurrentVibration, status);
- mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
- mCurrentVibration.opPkg);
-
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- mCurrentVibration = null;
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @VisibleForTesting
- void updateVibrators() {
- synchronized (mLock) {
- boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
- mVibrationSettings.shouldVibrateInputDevices());
-
- if (mCurrentVibration == null) {
- return;
- }
-
- if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
- mCurrentVibration.attrs.getUsage())) {
- // If the state changes out from under us then just reset.
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
-
- private boolean isSystemHapticFeedback(Vibration vib) {
- if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
- return false;
- }
- return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
- }
-
- private void dumpInternal(PrintWriter pw) {
- pw.println("Vibrator Service:");
- synchronized (mLock) {
- pw.print(" mCurrentVibration=");
- if (mCurrentVibration != null) {
- pw.println(mCurrentVibration.getDebugInfo().toString());
- } else {
- pw.println("null");
- }
- pw.print(" mCurrentExternalVibration=");
- if (mCurrentExternalVibration != null) {
- pw.println(mCurrentExternalVibration.getDebugInfo().toString());
- } else {
- pw.println("null");
- }
- pw.println(" mVibratorController=" + mVibratorController);
- pw.println(" mVibrationSettings=" + mVibrationSettings);
- pw.println();
- pw.println(" Previous ring vibrations:");
- for (Vibration.DebugInfo info : mPreviousRingVibrations) {
- pw.print(" ");
- pw.println(info.toString());
- }
-
- pw.println(" Previous notification vibrations:");
- for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous alarm vibrations:");
- for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous vibrations:");
- for (Vibration.DebugInfo info : mPreviousVibrations) {
- pw.println(" " + info);
- }
-
- pw.println(" Previous external vibrations:");
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- pw.println(" " + info);
- }
- }
- }
-
- private void dumpProto(FileDescriptor fd) {
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
- synchronized (mLock) {
- if (mCurrentVibration != null) {
- mCurrentVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_VIBRATION);
- }
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
- }
- proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
- proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- mVibratorController.isUnderExternalControl());
- mVibrationSettings.dumpProto(proto);
-
- for (Vibration.DebugInfo info : mPreviousRingVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
- }
-
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
- }
- }
- proto.flush();
- }
-
- /** Point of injection for test dependencies */
- @VisibleForTesting
- static class Injector {
-
- VibratorController createVibratorController(OnVibrationCompleteListener listener) {
- return new VibratorController(/* vibratorId= */ -1, listener);
- }
-
- Handler createHandler(Looper looper) {
- return new Handler(looper);
- }
-
- void addService(String name, IBinder service) {
- ServiceManager.addService(name, service);
- }
- }
-
- BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mLock) {
- // When the system is entering a non-interactive state, we want
- // to cancel vibrations in case a misbehaving app has abandoned
- // them. However it may happen that the system is currently playing
- // haptic feedback as part of the transition. So we don't cancel
- // system vibrations.
- if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
- }
- };
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- final long ident = Binder.clearCallingIdentity();
-
- boolean isDumpProto = false;
- for (String arg : args) {
- if (arg.equals("--proto")) {
- isDumpProto = true;
- }
- }
- try {
- if (isDumpProto) {
- dumpProto(fd);
- } else {
- dumpInternal(pw);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
- new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
- }
-
- final class ExternalVibratorService extends IExternalVibratorService.Stub {
- ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
-
- @Override
- public int onExternalVibrationStart(ExternalVibration vib) {
- if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
- return IExternalVibratorService.SCALE_MUTE;
- }
- if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
- vib.getUid(), -1 /*owningUid*/, true /*exported*/)
- != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
- + " tried to play externally controlled vibration"
- + " without VIBRATE permission, ignoring.");
- return IExternalVibratorService.SCALE_MUTE;
- }
-
- int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return IExternalVibratorService.SCALE_MUTE;
- }
-
- VibrationThread cancelingVibration = null;
- int scale;
- synchronized (mLock) {
- if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
- // We are already playing this external vibration, so we can return the same
- // scale calculated in the previous call to this method.
- return mCurrentExternalVibration.scale;
- }
- if (mCurrentExternalVibration == null) {
- // If we're not under external control right now, then cancel any normal
- // vibration that may be playing and ready the vibrator for external control.
- mNextVibrationThread = null;
- doCancelVibrateLocked(Vibration.Status.CANCELLED);
- cancelingVibration = mThread;
- } else {
- endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
- }
- // At this point we either have an externally controlled vibration playing, or
- // no vibration playing. Since the interface defines that only one externally
- // controlled vibration can play at a time, by returning something other than
- // SCALE_MUTE from this function we can be assured that if we are currently
- // playing vibration, it will be muted in favor of the new vibration.
- //
- // Note that this doesn't support multiple concurrent external controls, as we
- // would need to mute the old one still if it came from a different controller.
- mCurrentExternalVibration = new ExternalVibrationHolder(vib);
- mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
- vib.linkToDeath(mCurrentExternalDeathRecipient);
- mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
- vib.getVibrationAttributes().getUsage());
- scale = mCurrentExternalVibration.scale;
- }
- if (cancelingVibration != null) {
- try {
- cancelingVibration.join();
- } catch (InterruptedException e) {
- Slog.w("Interrupted while waiting current vibration to be cancelled before "
- + "starting external vibration", e);
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "Vibrator going under external control.");
- }
- mVibratorController.setExternalControl(true);
- if (DEBUG) {
- Slog.e(TAG, "Playing external vibration: " + vib);
- }
- return scale;
- }
-
- @Override
- public void onExternalVibrationStop(ExternalVibration vib) {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
- if (DEBUG) {
- Slog.e(TAG, "Stopping external vibration" + vib);
- }
- doCancelExternalVibrateLocked(Vibration.Status.FINISHED);
- }
- }
- }
-
- private void doCancelExternalVibrateLocked(Vibration.Status status) {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
- try {
- if (mCurrentExternalVibration == null) {
- return;
- }
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.unlinkToDeath(
- mCurrentExternalDeathRecipient);
- mCurrentExternalDeathRecipient = null;
- mCurrentExternalVibration = null;
- mVibratorController.setExternalControl(false);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
- public void binderDied() {
- synchronized (mLock) {
- if (mCurrentExternalVibration != null) {
- if (DEBUG) {
- Slog.d(TAG, "External vibration finished because binder died");
- }
- doCancelExternalVibrateLocked(Vibration.Status.CANCELLED);
- }
- }
- }
- }
- }
-
- private final class VibratorShellCommand extends ShellCommand {
-
- private final IBinder mToken;
-
- private final class CommonOptions {
- public boolean force = false;
- public void check(String opt) {
- switch (opt) {
- case "-f":
- force = true;
- break;
- }
- }
- }
-
- private VibratorShellCommand(IBinder token) {
- mToken = token;
- }
-
- @Override
- public int onCommand(String cmd) {
- if ("vibrate".equals(cmd)) {
- return runVibrate();
- } else if ("waveform".equals(cmd)) {
- return runWaveform();
- } else if ("prebaked".equals(cmd)) {
- return runPrebaked();
- } else if ("capabilities".equals(cmd)) {
- return runCapabilities();
- } else if ("cancel".equals(cmd)) {
- cancelVibrate(mToken);
- return 0;
- }
- return handleDefaultCommands(cmd);
- }
-
- private boolean checkDoNotDisturb(CommonOptions opts) {
- if (mVibrationSettings.isInZenMode() && !opts.force) {
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.print("Ignoring because device is on DND mode ");
- return true;
- }
- }
- return false;
- }
-
- private int runVibrate() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
- try {
- CommonOptions commonOptions = new CommonOptions();
-
- String opt;
- while ((opt = getNextOption()) != null) {
- commonOptions.check(opt);
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- final long duration = Long.parseLong(getNextArgRequired());
- String description = getNextArg();
- if (description == null) {
- description = "Shell command";
- }
-
- VibrationEffect effect =
- VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runWaveform() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
- try {
- String description = "Shell command";
- int repeat = -1;
- ArrayList<Integer> amplitudesList = null;
- CommonOptions commonOptions = new CommonOptions();
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-d":
- description = getNextArgRequired();
- break;
- case "-r":
- repeat = Integer.parseInt(getNextArgRequired());
- break;
- case "-a":
- if (amplitudesList == null) {
- amplitudesList = new ArrayList<Integer>();
- }
- break;
- default:
- commonOptions.check(opt);
- break;
- }
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- ArrayList<Long> timingsList = new ArrayList<Long>();
-
- String arg;
- while ((arg = getNextArg()) != null) {
- if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
- amplitudesList.add(Integer.parseInt(arg));
- } else {
- timingsList.add(Long.parseLong(arg));
- }
- }
-
- VibrationEffect effect;
- long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
- if (amplitudesList == null) {
- effect = VibrationEffect.createWaveform(timings, repeat);
- } else {
- int[] amplitudes =
- amplitudesList.stream().mapToInt(Integer::intValue).toArray();
- effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
- }
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runPrebaked() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
- try {
- CommonOptions commonOptions = new CommonOptions();
- boolean shouldFallback = false;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- if ("-b".equals(opt)) {
- shouldFallback = true;
- } else {
- commonOptions.check(opt);
- }
- }
-
- if (checkDoNotDisturb(commonOptions)) {
- return 0;
- }
-
- final int id = Integer.parseInt(getNextArgRequired());
-
- String description = getNextArg();
- if (description == null) {
- description = "Shell command";
- }
-
- VibrationEffect effect = VibrationEffect.get(id, shouldFallback);
- VibrationAttributes attrs = createVibrationAttributes(commonOptions);
- vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
- mToken);
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private int runCapabilities() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities");
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.println("Vibrator capabilities:");
- if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
- pw.println(" Always on effects");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
- pw.println(" Compose effects");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
- pw.println(" Amplitude control");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
- pw.println(" External control");
- }
- if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
- pw.println(" External amplitude control");
- }
- pw.println("");
- return 0;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
- final int flags = commonOptions.force
- ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
- : 0;
- return new VibrationAttributes.Builder()
- .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
- // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
- .setUsage(VibrationAttributes.USAGE_TOUCH)
- .build();
- }
-
- @Override
- public void onHelp() {
- try (PrintWriter pw = getOutPrintWriter();) {
- pw.println("Vibrator commands:");
- pw.println(" help");
- pw.println(" Prints this help text.");
- pw.println("");
- pw.println(" vibrate duration [description]");
- pw.println(" Vibrates for duration milliseconds; ignored when device is on ");
- pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
- pw.println(" waveform [-d description] [-r index] [-a] duration [amplitude] ...");
- pw.println(" Vibrates for durations and amplitudes in list; ignored when ");
- pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength ");
- pw.println(" user setting will be used to scale amplitude.");
- pw.println(" If -r is provided, the waveform loops back to the specified");
- pw.println(" index (e.g. 0 loops from the beginning)");
- pw.println(" If -a is provided, the command accepts duration-amplitude pairs;");
- pw.println(" otherwise, it accepts durations only and alternates off/on");
- pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255.");
- pw.println(" prebaked [-b] effect-id [description]");
- pw.println(" Vibrates with prebaked effect; ignored when device is on DND ");
- pw.println(" (Do Not Disturb) mode; touch feedback strength user setting ");
- pw.println(" will be used to scale amplitude.");
- pw.println(" If -b is provided, the prebaked fallback effect will be played if");
- pw.println(" the device doesn't support the given effect-id.");
- pw.println(" capabilities");
- pw.println(" Prints capabilities of this device.");
- pw.println(" cancel");
- pw.println(" Cancels any active vibration");
- pw.println("Common Options:");
- pw.println(" -f - Force. Ignore Do Not Disturb setting.");
- pw.println("");
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/services/core/java/com/android/server/accounts/OWNERS
+++ b/services/core/java/com/android/server/accounts/OWNERS
@@ -3,7 +3,6 @@
sandrakwan@google.com
hackbod@google.com
svetoslavganov@google.com
-moltmann@google.com
fkupolov@google.com
yamasani@google.com
omakoto@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f0f29a9..bedd19b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1490,7 +1490,9 @@
private static final int INDEX_TOTAL_SWAP_PSS = 10;
private static final int INDEX_TOTAL_RSS = 11;
private static final int INDEX_TOTAL_NATIVE_PSS = 12;
- private static final int INDEX_LAST = 13;
+ private static final int INDEX_TOTAL_MEMTRACK_GRAPHICS = 13;
+ private static final int INDEX_TOTAL_MEMTRACK_GL = 14;
+ private static final int INDEX_LAST = 15;
final class UiHandler extends Handler {
public UiHandler() {
@@ -10316,6 +10318,7 @@
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+ long[] memtrackTmp = new long[4];
long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
@@ -10349,6 +10352,8 @@
final int reportType;
final long startTime;
final long endTime;
+ long memtrackGraphics = 0;
+ long memtrackGl = 0;
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
startTime = SystemClock.currentThreadTimeMillis();
@@ -10360,7 +10365,7 @@
} else {
reportType = ProcessStats.ADD_PSS_EXTERNAL;
startTime = SystemClock.currentThreadTimeMillis();
- long pss = Debug.getPss(pid, tmpLong, null);
+ long pss = Debug.getPss(pid, tmpLong, memtrackTmp);
if (pss == 0) {
continue;
}
@@ -10368,6 +10373,8 @@
endTime = SystemClock.currentThreadTimeMillis();
mi.dalvikPrivateDirty = (int) tmpLong[0];
mi.dalvikRss = (int) tmpLong[2];
+ memtrackGraphics = memtrackTmp[1];
+ memtrackGl = memtrackTmp[2];
}
if (!opts.isCheckinRequest && opts.dumpDetails) {
pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
@@ -10431,6 +10438,8 @@
ss[INDEX_TOTAL_PSS] += myTotalPss;
ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
ss[INDEX_TOTAL_RSS] += myTotalRss;
+ ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+ ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
(hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
myTotalSwapPss, myTotalRss, pid, hasActivities);
@@ -10494,6 +10503,8 @@
final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1];
mAppProfiler.forAllCpuStats((st) -> {
if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+ long memtrackGraphics = 0;
+ long memtrackGl = 0;
if (memInfos[0] == null) {
memInfos[0] = new Debug.MemoryInfo();
}
@@ -10503,13 +10514,15 @@
return;
}
} else {
- long pss = Debug.getPss(st.pid, tmpLong, null);
+ long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp);
if (pss == 0) {
return;
}
info.nativePss = (int) pss;
info.nativePrivateDirty = (int) tmpLong[0];
info.nativeRss = (int) tmpLong[2];
+ memtrackGraphics = memtrackTmp[1];
+ memtrackGl = memtrackTmp[2];
}
final long myTotalPss = info.getTotalPss();
@@ -10519,6 +10532,8 @@
ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
ss[INDEX_TOTAL_RSS] += myTotalRss;
ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss;
+ ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+ ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
@@ -10726,7 +10741,11 @@
pw.print(" mapped + ");
pw.print(stringifyKBSize(dmabufUnmapped));
pw.println(" unmapped)");
- kernelUsed += totalExportedDmabuf;
+ // Account unmapped dmabufs as part of kernel memory allocations
+ kernelUsed += dmabufUnmapped;
+ // Replace memtrack HAL reported Graphics category with mapped dmabufs
+ ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS];
+ ss[INDEX_TOTAL_PSS] += dmabufMapped;
}
final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
if (totalDmabufHeapPool >= 0) {
@@ -10736,13 +10755,27 @@
}
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
- pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage));
+ final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+ if (gpuDmaBufUsage >= 0) {
+ final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+ pw.print(" GPU: ");
+ pw.print(stringifyKBSize(gpuUsage));
+ pw.print(" (");
+ pw.print(stringifyKBSize(gpuDmaBufUsage));
+ pw.print(" dmabuf + ");
+ pw.print(stringifyKBSize(gpuPrivateUsage));
+ pw.println(" private)");
+ // Replace memtrack HAL reported GL category with private GPU allocations and
+ // account it as part of kernel memory allocations
+ ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GL];
+ kernelUsed += gpuPrivateUsage;
+ } else {
+ pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage));
+ }
}
- /*
- * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
- * memInfo.getCachedSizeKb().
- */
+ // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+ // memInfo.getCachedSizeKb().
final long lostRAM = memInfo.getTotalSizeKb()
- (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3ff5872..fc7a476 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1322,7 +1322,7 @@
infoMap.put(mi.pid, mi);
}
updateCpuStatsNow();
- long[] memtrackTmp = new long[1];
+ long[] memtrackTmp = new long[4];
long[] swaptrackTmp = new long[2];
// Get a list of Stats that have vsize > 0
final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0);
@@ -1345,6 +1345,8 @@
long totalPss = 0;
long totalSwapPss = 0;
long totalMemtrack = 0;
+ long totalMemtrackGraphics = 0;
+ long totalMemtrackGl = 0;
for (int i = 0, size = memInfos.size(); i < size; i++) {
ProcessMemInfo mi = memInfos.get(i);
if (mi.pss == 0) {
@@ -1355,6 +1357,8 @@
totalPss += mi.pss;
totalSwapPss += mi.swapPss;
totalMemtrack += mi.memtrack;
+ totalMemtrackGraphics += memtrackTmp[1];
+ totalMemtrackGl += memtrackTmp[2];
}
Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
@Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
@@ -1521,10 +1525,16 @@
} else {
final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
if (totalExportedDmabuf >= 0) {
+ final long dmabufMapped = Debug.getDmabufMappedSizeKb();
+ final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
memInfoBuilder.append("DMA-BUF: ");
memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
memInfoBuilder.append("\n");
- kernelUsed += totalExportedDmabuf;
+ // Account unmapped dmabufs as part of kernel memory allocations
+ kernelUsed += dmabufUnmapped;
+ // Replace memtrack HAL reported Graphics category with mapped dmabufs
+ totalPss -= totalMemtrackGraphics;
+ totalPss += dmabufMapped;
}
final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
@@ -1537,19 +1547,34 @@
final long gpuUsage = Debug.getGpuTotalUsageKb();
if (gpuUsage >= 0) {
- memInfoBuilder.append(" GPU: ");
- memInfoBuilder.append(stringifyKBSize(gpuUsage));
- memInfoBuilder.append("\n");
+ final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+ if (gpuDmaBufUsage >= 0) {
+ final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+ memInfoBuilder.append(" GPU: ");
+ memInfoBuilder.append(stringifyKBSize(gpuUsage));
+ memInfoBuilder.append(" (");
+ memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage));
+ memInfoBuilder.append(" dmabuf + ");
+ memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage));
+ memInfoBuilder.append(" private)\n");
+ // Replace memtrack HAL reported GL category with private GPU allocations and
+ // account it as part of kernel memory allocations
+ totalPss -= totalMemtrackGl;
+ kernelUsed += gpuPrivateUsage;
+ } else {
+ memInfoBuilder.append(" GPU: ");
+ memInfoBuilder.append(stringifyKBSize(gpuUsage));
+ memInfoBuilder.append("\n");
+ }
+
}
memInfoBuilder.append(" Used RAM: ");
memInfoBuilder.append(stringifyKBSize(
totalPss - cachedPss + kernelUsed));
memInfoBuilder.append("\n");
- /*
- * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
- * memInfo.getCachedSizeKb().
- */
+ // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+ // memInfo.getCachedSizeKb().
memInfoBuilder.append(" Lost RAM: ");
memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
- (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..52bb55f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
*/
package com.android.server.am;
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
import android.annotation.Nullable;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
import android.telephony.TelephonyManager;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
private WifiActivityEnergyInfo mLastWifiInfo =
new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
- /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+ /**
+ * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+ * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+ */
+ // TODO(b/180029015): Hook this up (it isn't used yet)
@GuardedBy("mWorkerLock")
- private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
- /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
- @GuardedBy("mWorkerLock")
- private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+ private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
/** Snapshot of measured energies, or null if no measured energies are supported. */
@GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
mWifiManager = wm;
mTelephony = tm;
mPowerStatsInternal = psi;
+
+ boolean[] supportedStdBuckets = null;
+ int numCustomBuckets = 0;
if (mPowerStatsInternal != null) {
- populateEnergyConsumerSubsystemMapsLocked();
- final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
- mMeasuredEnergySnapshot = initialMeasuredEnergies == null
- ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
- final boolean[] supportedStdBuckets
- = getSupportedEnergyBuckets(initialMeasuredEnergies);
- final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
- synchronized (mStats) {
- mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+ final SparseArray<EnergyConsumer> idToConsumer
+ = populateEnergyConsumerSubsystemMapsLocked();
+ if (idToConsumer != null) {
+ mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+ final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+ // According to spec, initialEcrs will include 0s for consumers that haven't
+ // used any energy yet, as long as they are supported; however, attributed uid
+ // energies will be absent if their energy is 0.
+ mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+ numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+ supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
}
}
+ synchronized (mStats) {
+ mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+ }
}
}
@@ -568,7 +573,9 @@
} catch (ExecutionException e) {
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
}
- final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+ final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+ mMeasuredEnergySnapshot == null ? null :
mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
final long elapsedRealtimeUs = elapsedRealtime * 1000;
final long uptimeUs = uptime * 1000;
+ // Now that we have finally received all the data, we can tell mStats about it.
synchronized (mStats) {
mStats.addHistoryEventLocked(
elapsedRealtime,
@@ -601,10 +609,21 @@
}
// Inform mStats about each applicable measured energy.
- if (energyDeltas != null) {
- final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
- // Always pass in what BatteryExternalStatsWorker thinks screenState is.
- mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+ if (measuredEnergyDeltas != null) {
+ final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+ if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+ // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+ mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+ }
+ }
+ // Inform mStats about each applicable custom energy bucket.
+ if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+ // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+ for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+ long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+ SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+ mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+ }
}
if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
if (wifiInfo != null) {
if (wifiInfo.isValid()) {
- // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+ // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+ // .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
mStats.updateWifiState(extractDeltaLocked(wifiInfo)
/*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
} else {
@@ -740,21 +760,23 @@
}
/**
- * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+ * Map the {@link EnergyConsumerType}s in the given energyArray to
* their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
* Does not include custom energy buckets (which are always, by definition, supported).
*
* @return array with true for index i if standard energy bucket i is supported.
*/
- private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
- if (energyArray == null) {
+ private static @Nullable boolean[] getSupportedEnergyBuckets(
+ SparseArray<EnergyConsumer> idToConsumer) {
+ if (idToConsumer == null) {
return null;
}
final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
- final int size = energyArray.size();
- for (int energyIdx = 0; energyIdx < size; energyIdx++) {
- switch (energyArray.getSubsystem(energyIdx)) {
- case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+ final int size = idToConsumer.size();
+ for (int idx = 0; idx < size; idx++) {
+ final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+ switch (consumer.type) {
+ case EnergyConsumerType.DISPLAY:
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
return buckets;
}
- /**
- * Get a {@link MeasuredEnergyArray} with the latest
- * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
- *
- * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
- * EnergyConsumerResult}[]
- */
+ /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
@GuardedBy("mWorkerLock")
- @VisibleForTesting
- public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
- final EnergyConsumerResult[] results;
+ private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
try {
- results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+ return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
return null;
}
- if (results == null) return null;
- final int size = results.length;
- final int[] subsystems = new int[size];
- final long[] energyUJ = new long[size];
-
- int count = 0;
- for (int i = 0; i < size; i++) {
- final EnergyConsumerResult consumer = results[i];
- final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
- MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
- if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
- subsystems[count] = subsystem;
- energyUJ[count] = consumer.energyUWs;
- count++;
- }
- final int arraySize = count;
- return new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- if (index >= size()) {
- throw new IllegalArgumentException(
- "Out of bounds subsystem index! index : " + index + ", size : "
- + size());
- }
- return subsystems[index];
- }
-
- @Override
- public long getEnergy(int index) {
- if (index >= size()) {
- throw new IllegalArgumentException(
- "Out of bounds subsystem index! index : " + index + ", size : "
- + size());
- }
- return energyUJ[index];
- }
-
- @Override
- public int size() {
- return arraySize;
- }
- };
}
- /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+ /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
@GuardedBy("mWorkerLock")
- private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+ private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+ {
if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
if (flags == UPDATE_ALL) {
@@ -836,72 +809,62 @@
return getEnergyConsumptionData();
}
- final List<Integer> energySubsystems = new ArrayList<>();
+ final List<Integer> energyConsumerIds = new ArrayList<>();
if ((flags & UPDATE_DISPLAY) != 0) {
- addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+ addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
}
// TODO: Wifi, Bluetooth, etc., go here
- if (energySubsystems.isEmpty()) {
+
+ if (energyConsumerIds.isEmpty()) {
return null;
}
- // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
+ // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
return getEnergyConsumptionData();
}
@GuardedBy("mWorkerLock")
- private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
- @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
- if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
- energyConsumerIds.add(consumerId);
- }
+ private void addEnergyConsumerIdLocked(
+ List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+ final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
+ energyConsumerIds.add(consumerId);
}
+ /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
@GuardedBy("mWorkerLock")
- private void populateEnergyConsumerSubsystemMapsLocked() {
+ private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
if (mPowerStatsInternal == null) {
- // PowerStatsInternal unavailable, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
+ return null;
}
final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
- if (energyConsumers == null) {
- // EnergyConsumer data unavailable, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
+ if (energyConsumers == null || energyConsumers.length == 0) {
+ return null;
}
- final int length = energyConsumers.length;
- if (length == 0) {
- // EnergyConsumer array empty, don't bother populating maps.
- mEnergyConsumerToSubsystemMap = null;
- mSubsystemToEnergyConsumerMap = null;
- return;
- } else {
- mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
- mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
- }
+ // TODO(b/180029015): Initialize typeToIds
+ // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+ // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+ // Maps id -> EnergyConsumer (1:1 map)
+ final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
// Add all expected EnergyConsumers to the maps
- for (int i = 0; i < length; i++) {
- final EnergyConsumer consumer = energyConsumers[i];
- switch (consumer.type) {
- case EnergyConsumerType.DISPLAY:
- if (consumer.ordinal == 0) {
- mEnergyConsumerToSubsystemMap.put(consumer.id,
- MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
- mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
- consumer.id);
- } else {
- Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
- + ") for EnergyConsumerType.DISPLAY");
- }
- break;
- default:
- Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+ for (final EnergyConsumer consumer : energyConsumers) {
+ // Check for inappropriate ordinals
+ if (consumer.ordinal != 0) {
+ switch (consumer.type) {
+ case EnergyConsumerType.OTHER:
+ case EnergyConsumerType.CPU_CLUSTER:
+ break;
+ default:
+ Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+ + consumer.ordinal + " for type " + consumer.type);
+ continue; // Ignore this consumer
+ }
}
-
+ idToConsumer.put(consumer.id, consumer);
+ // TODO(b/180029015): Also populate typeToIds map
}
+ // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+ return idToConsumer;
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7e65434..8b6fabd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -967,6 +967,12 @@
}
}
+ static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+ return r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+ }
+
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
@@ -1349,14 +1355,18 @@
< brOptions.getMinManifestReceiverApiLevel() ||
info.activityInfo.applicationInfo.targetSdkVersion
> brOptions.getMaxManifestReceiverApiLevel())) {
+ Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+ + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+ + " but delivery restricted to ["
+ + brOptions.getMinManifestReceiverApiLevel() + ", "
+ + brOptions.getMaxManifestReceiverApiLevel()
+ + "] broadcasting " + broadcastDescription(r, component));
skip = true;
}
if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
Slog.w(TAG, "Association not allowed: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+ + broadcastDescription(r, component));
skip = true;
}
if (!skip) {
@@ -1364,9 +1374,7 @@
r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
if (skip) {
Slog.w(TAG, "Firewall blocked: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+ + broadcastDescription(r, component));
}
}
int perm = mService.checkComponentPermission(info.activityInfo.permission,
@@ -1375,18 +1383,12 @@
if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
if (!info.activityInfo.exported) {
Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ")"
- + " is not exported from uid " + info.activityInfo.applicationInfo.uid
- + " due to receiver " + component.flattenToShortString());
+ + broadcastDescription(r, component)
+ + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
} else {
Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ")"
- + " requires " + info.activityInfo.permission
- + " due to receiver " + component.flattenToShortString());
+ + broadcastDescription(r, component)
+ + " requires " + info.activityInfo.permission);
}
skip = true;
} else if (!skip && info.activityInfo.permission != null) {
@@ -1396,13 +1398,9 @@
"Broadcast delivered to " + info.activityInfo.name)
!= AppOpsManager.MODE_ALLOWED) {
Slog.w(TAG, "Appop Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid="
- + r.callingPid + ", uid=" + r.callingUid + ")"
+ + broadcastDescription(r, component)
+ " requires appop " + AppOpsManager.permissionToOp(
- info.activityInfo.permission)
- + " due to registered receiver "
- + component.flattenToShortString());
+ info.activityInfo.permission));
skip = true;
}
}
@@ -1520,7 +1518,7 @@
+ info.activityInfo.packageName, e);
}
if (!isAvailable) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+ Slog.w(TAG_BROADCAST,
"Skipping delivery to " + info.activityInfo.packageName + " / "
+ info.activityInfo.applicationInfo.uid
+ " : package no longer available");
@@ -1536,6 +1534,9 @@
if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
info.activityInfo.packageName, UserHandle.getUserId(
info.activityInfo.applicationInfo.uid))) {
+ Slog.w(TAG_BROADCAST,
+ "Skipping delivery: permission review required for "
+ + broadcastDescription(r, component));
skip = true;
}
}
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
package com.android.server.am;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
import java.io.PrintWriter;
-import java.util.Arrays;
/**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
*/
@VisibleForTesting
public class MeasuredEnergySnapshot {
private static final String TAG = "MeasuredEnergySnapshot";
- private static final long UNAVAILABLE = -1;
+ public static final long UNAVAILABLE = -1L;
+
+ /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+ private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+ /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+ private final int mNumOtherOrdinals;
/**
- * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+ * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+ * each {@link EnergyConsumer} was updated.
*
- * Note that the snapshots for different subsystems may have been taken at different times.
+ * Note that the snapshots for different ids may have been taken at different times.
+ * Note that energies for all existing ids are stored here, including each ordinal of type
+ * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
*
- * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+ * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
*/
- private final long[] mMeasuredEnergySnapshots;
+ private final SparseLongArray mMeasuredEnergySnapshots;
/**
- * Constructor that initializes to the given energyArray;
- * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+ * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+ * {@link EnergyConsumerType#OTHER} was updated.
+ * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+ * uid to an energy (UJ). That is,
+ * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+ *
+ * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+ * If an id is present but a uid is not present, that uid's energy is 0.
*/
- public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
- this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+ private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+ /**
+ * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+ * exist and what their details are.
+ */
+ MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+ mEnergyConsumers = idToConsumerMap;
+ mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+ mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+ mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
}
/**
- * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
- * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+ * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+ * custom energy buckets supported by the device.
*/
- @VisibleForTesting
- MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
- if (initialEnergyArray.size() > numSubsystems) {
- throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
- + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
- }
- mMeasuredEnergySnapshots = new long[numSubsystems];
- Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
- fillGivenSubsystems(initialEnergyArray);
+ public int getNumOtherOrdinals() {
+ return mNumOtherOrdinals;
}
- /**
- * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
- * energy values from energyArray.
- */
- private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
- final int size = energyArray.size();
- for (int i = 0; i < size; i++) {
- final int subsystem = energyArray.getSubsystem(i);
- mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
- }
+ /** Class for returning measured energy delta data. */
+ static class MeasuredEnergyDeltaData {
+ /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+ public long displayEnergyUJ = UNAVAILABLE;
+
+ /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+ public @Nullable long[] otherTotalEnergyUJ = null;
+
+ /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+ public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
}
/**
* Update with the some freshly measured energies and return the difference (delta)
* between the previously stored values and the passed-in values.
*
- * @param energyArray measured energy array for some (possibly not all) subsystems.
+ * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+ * Consumers that are not present are ignored (they are *not* treated as 0).
*
- * @return a map from the updated subsystems to their corresponding energy deltas.
- * Subsystems not present in energyArray will not appear.
- * Subsystems with no difference in energy will not appear.
- * Returns null, if energyArray is null.
+ * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+ * their corresponding energy deltas.
+ * Fields with no interesting data (consumers not present in ecrs or with no energy
+ * difference) will generally be left as their default values.
+ * otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+ * length {@link #getNumOtherOrdinals()}.
+ * Returns null, if ecrs is null or empty.
*/
- public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
- if (energyArray == null) {
+ public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+ if (ecrs == null || ecrs.length == 0) {
return null;
}
- final SparseLongArray delta = new SparseLongArray();
- final int size = energyArray.size();
- for (int i = 0; i < size; i++) {
- final int updatedSubsystem = energyArray.getSubsystem(i);
- final long newEnergyUJ = energyArray.getEnergy(i);
- final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+ final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
- // If this is the first valid energy, there is no delta to take.
- if (oldEnergyUJ < 0) continue;
- final long deltaUJ = newEnergyUJ - oldEnergyUJ;
- if (deltaUJ == 0) continue;
- if (deltaUJ < 0) {
- Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
- + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+ for (final EnergyConsumerResult ecr : ecrs) {
+ // Extract the new energy data for the current consumer.
+ final int consumerId = ecr.id;
+ final long newEnergyUJ = ecr.energyUWs;
+ final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+ // Look up the static information about this consumer.
+ final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+ if (consumer == null) {
+ Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
continue;
}
- delta.put(updatedSubsystem, deltaUJ);
+ final int type = consumer.type;
+ final int ordinal = consumer.ordinal;
+
+ // Look up, and update, the old energy information about this consumer.
+ final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+ mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+ final SparseLongArray otherUidEnergies
+ = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+ // Everything is fully done being updated. We now calculate the delta for returning.
+
+ // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+ // there's no attribution either. Technically that isn't enforced at the HAL, but we
+ // can't really trust data like that anyway.
+
+ if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+ if (newEnergyUJ == oldEnergyUJ) continue;
+ final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+ if (deltaUJ < 0) {
+ Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+ + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+ continue;
+ }
+
+ switch (type) {
+ case EnergyConsumerType.DISPLAY:
+ output.displayEnergyUJ = deltaUJ;
+ break;
+ case EnergyConsumerType.OTHER:
+ if (output.otherTotalEnergyUJ == null) {
+ output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+ output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+ }
+ output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+ output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+ break;
+ default:
+ Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+ }
}
-
- fillGivenSubsystems(energyArray);
-
- return delta;
+ return output;
}
/**
- * Check if a subsystem's measured energy is available.
- * @param subsystem which subsystem.
- * @return true if subsystem is available.
+ * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+ * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+ * difference (delta) between the previously stored values and the passed-in values.
+ *
+ * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+ * @param newAttributions Record of uids and their new energyUJ values.
+ * Any uid not present is treated as having energy 0.
+ * If null or empty, all uids are treated as having energy 0.
+ * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+ * consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+ * Returns null if no delta available to calculate.
*/
- public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
- return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+ private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+ @NonNull EnergyConsumer consumerInfo,
+ @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+ if (consumerInfo.type != EnergyConsumerType.OTHER) {
+ return null;
+ }
+ if (newAttributions == null) {
+ // Treat null as empty (i.e. all uids have 0 energy).
+ newAttributions = new EnergyConsumerAttribution[0];
+ }
+
+ // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+ SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+ // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+ if (uidOldEnergyMap == null) {
+ uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+ mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+ for (EnergyConsumerAttribution newAttribution : newAttributions) {
+ uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+ }
+ return null;
+ }
+
+ // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+ final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+ for (EnergyConsumerAttribution newAttribution : newAttributions) {
+ final int uid = newAttribution.uid;
+ final long newEnergyUJ = newAttribution.energyUWs;
+ // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+ final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+ uidOldEnergyMap.put(uid, newEnergyUJ);
+
+ // Everything is fully done being updated. We now calculate the delta for returning.
+ if (oldEnergyUJ < 0) continue;
+ if (newEnergyUJ == oldEnergyUJ) continue;
+ final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+ if (deltaUJ < 0) {
+ Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+ + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+ continue;
+ }
+ uidEnergyDeltas.put(uid, deltaUJ);
+ }
+ return uidEnergyDeltas;
}
/** Dump debug data. */
public void dump(PrintWriter pw) {
- pw.println("Measured energy snapshot (microjoules):");
- pw.print(" ");
- for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
- final long energyUJ = mMeasuredEnergySnapshots[i];
- if (energyUJ == UNAVAILABLE) continue;
- pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
- pw.print(" : ");
- pw.print(energyUJ);
- if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
- pw.print(", ");
- }
+ pw.println("Measured energy snapshot");
+ pw.println("List of EnergyConsumers:");
+ for (int i = 0; i < mEnergyConsumers.size(); i++) {
+ final int id = mEnergyConsumers.keyAt(i);
+ final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+ pw.println(String.format(" Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+ consumer.id, consumer.ordinal, consumer.type, consumer.name));
}
+ pw.println("Map of consumerIds to energy (in microjoules):");
+ for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
+ final int id = mMeasuredEnergySnapshots.keyAt(i);
+ final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+ pw.println(String.format(" Consumer %d has energy %d uJ}", id, energyUJ));
+ }
+ pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+ pw.println(" " + mAttributionSnapshots);
pw.println();
}
+
+ /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+ private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+ if (idToConsumer == null) return 0;
+ int numOtherOrdinals = 0;
+ final int size = idToConsumer.size();
+ for (int idx = 0; idx < size; idx++) {
+ final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+ if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+ }
+ return numOtherOrdinals;
+ }
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 10fe1e1..c92abd4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3131,11 +3131,6 @@
return AppOpsManager.MODE_ERRORED;
}
final Op op = getOpLocked(ops, code, uid, true);
- if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
- AppOpsManager.MODE_IGNORED);
- return AppOpsManager.MODE_IGNORED;
- }
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
@@ -3145,6 +3140,12 @@
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
+ if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+ attributedOp.rejected(uidState.state, flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+ AppOpsManager.MODE_IGNORED);
+ return AppOpsManager.MODE_IGNORED;
+ }
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index c956043..f888200 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -257,11 +257,15 @@
mUserId,
mOpPackageName,
mOperationId);
+ mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
+ } else {
+ // The UI was already showing :)
+ mState = STATE_AUTH_STARTED_UI_SHOWING;
}
- mState = STATE_AUTH_STARTED;
+
}
}
@@ -794,7 +798,7 @@
case BiometricAuthenticator.TYPE_FINGERPRINT:
return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
case BiometricAuthenticator.TYPE_FACE:
- return FaceManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
+ return FaceManager.getAuthHelpMessage(mContext, acquiredInfo, vendorCode);
default:
return null;
}
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 baeb3fd..4925ce0 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
@@ -435,13 +435,7 @@
mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> {
- if (mTestHalEnabled) {
- return new TestSession(mCurrentSession.mHalSessionCallback);
- } else {
- return mCurrentSession != null ? mCurrentSession.mSession : null;
- }
- };
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}
@NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -496,6 +490,11 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled: " + enabled);
+ if (enabled != mTestHalEnabled) {
+ // The framework should retrieve a new session from the HAL.
+ mCurrentSession = null;
+ }
mTestHalEnabled = enabled;
}
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 1d57073..ff65c93 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
@@ -17,92 +17,119 @@
package com.android.server.biometrics.sensors.face.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.Error;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.ISessionCallback;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.common.NativeHandle;
import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only no-ops.
*/
public class TestHal extends IFace.Stub {
+ private static final String TAG = "face.aidl.TestHal";
@Override
public SensorProps[] getSensorProps() {
+ Slog.w(TAG, "getSensorProps");
return new SensorProps[0];
}
@Override
public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
- return new ISession() {
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
+ Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+ return new ISession.Stub() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) {
-
+ public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ cb.onChallengeRevoked(challenge);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
byte enrollmentType, byte[] features, NativeHandle previewSurface) {
- return null;
+ Slog.w(TAG, "enroll, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
- return null;
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
- return null;
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
- public void enumerateEnrollments(int cookie) {
-
+ public void enumerateEnrollments(int cookie) throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getFeatures(int cookie, int enrollmentId) {
-
+ public void getFeatures(int cookie, int enrollmentId) throws RemoteException {
+ Slog.w(TAG, "getFeatures, cookie: " + cookie);
+ cb.onFeaturesRetrieved(new byte[0], enrollmentId);
}
@Override
public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
- byte feature, boolean enabled) {
-
+ byte feature, boolean enabled) throws RemoteException {
+ Slog.w(TAG, "setFeature, cookie: " + cookie);
+ cb.onFeatureSet(enrollmentId, feature);
}
@Override
- public void getAuthenticatorId(int cookie) {
-
+ public void getAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) {
-
+ public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
-
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
+ public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ cb.onLockoutCleared();
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
deleted file mode 100644
index 23e6988..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.face.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.face.ISession;
-import android.hardware.common.NativeHandle;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-public class TestSession extends ISession.Stub {
- private static final String TAG = "FaceTestSession";
-
- @NonNull
- private final Sensor.HalSessionCallback mHalSessionCallback;
-
- TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
- mHalSessionCallback = halSessionCallback;
- }
-
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
- mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
- }
-
- @Override
- public void revokeChallenge(int cookie, long challenge) {
- mHalSessionCallback.onChallengeRevoked(challenge);
- }
-
- @Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType,
- byte[] features, NativeHandle previewSurface) {
- return null;
- }
-
- @Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- return new ICancellationSignal() {
- @Override
- public void cancel() {
- mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
- };
- }
-
- @Override
- public ICancellationSignal detectInteraction(int cookie) {
- return null;
- }
-
- @Override
- public void enumerateEnrollments(int cookie) {
-
- }
-
- @Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
- }
-
- @Override
- public void getFeatures(int cookie, int enrollmentId) {
-
- }
-
- @Override
- public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature,
- boolean enabled) {
-
- }
-
- @Override
- public void getAuthenticatorId(int cookie) {
- Slog.d(TAG, "getAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdRetrieved(0);
- }
-
- @Override
- public void invalidateAuthenticatorId(int cookie) {
- Slog.d(TAG, "invalidateAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdInvalidated(0);
- }
-
- @Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
- mHalSessionCallback.onLockoutCleared();
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index bab1114d..13bd1c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -25,10 +25,12 @@
import android.hardware.biometrics.face.V1_1.IBiometricsFace;
import android.os.NativeHandle;
import android.os.RemoteException;
+import android.util.Slog;
import java.util.ArrayList;
public class TestHal extends IBiometricsFace.Stub {
+ private static final String TAG = "face.hidl.TestHal";
@Nullable
private IBiometricsFaceClientCallback mCallback;
@@ -47,6 +49,7 @@
@Override
public OptionalUint64 generateChallenge(int challengeTimeoutSec) {
+ Slog.w(TAG, "generateChallenge");
final OptionalUint64 result = new OptionalUint64();
result.status = Status.OK;
result.value = 0;
@@ -55,6 +58,7 @@
@Override
public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) {
+ Slog.w(TAG, "enroll");
return 0;
}
@@ -94,17 +98,23 @@
}
@Override
- public int enumerate() {
+ public int enumerate() throws RemoteException {
+ Slog.w(TAG, "enumerate");
+ if (mCallback != null) {
+ mCallback.onEnumerate(0 /* deviceId */, new ArrayList<>(), 0 /* userId */);
+ }
return 0;
}
@Override
public int remove(int faceId) {
+ Slog.w(TAG, "remove");
return 0;
}
@Override
public int authenticate(long operationId) {
+ Slog.w(TAG, "authenticate");
return 0;
}
@@ -115,6 +125,7 @@
@Override
public int resetLockout(ArrayList<Byte> hat) {
+ Slog.w(TAG, "resetLockout");
return 0;
}
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 7e4ee9e7..c83c0fb 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
@@ -415,13 +415,7 @@
mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
- mLazySession = () -> {
- if (mTestHalEnabled) {
- return new TestSession(mCurrentSession.mHalSessionCallback);
- } else {
- return mCurrentSession != null ? mCurrentSession.mSession : null;
- }
- };
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}
@NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -476,6 +470,11 @@
}
void setTestHalEnabled(boolean enabled) {
+ Slog.w(mTag, "setTestHalEnabled: " + enabled);
+ if (enabled != mTestHalEnabled) {
+ // The framework should retrieve a new session from the HAL.
+ mCurrentSession = null;
+ }
mTestHalEnabled = enabled;
}
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 a31bcdc..8ed24b6 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
@@ -17,94 +17,120 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only provides no-ops.
*/
public class TestHal extends IFingerprint.Stub {
+ private static final String TAG = "fingerprint.aidl.TestHal";
+
@Override
public SensorProps[] getSensorProps() {
+ Slog.w(TAG, "getSensorProps");
return new SensorProps[0];
}
@Override
public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
- return new ISession() {
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
+ Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+ return new ISession.Stub() {
+ @Override
+ public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+ Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+ cb.onChallengeGenerated(0L);
}
@Override
- public void revokeChallenge(int cookie, long challenge) {
-
+ public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+ Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+ cb.onChallengeRevoked(challenge);
}
@Override
public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
- return null;
+ Slog.w(TAG, "enroll, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal authenticate(int cookie, long operationId) {
- return null;
+ Slog.w(TAG, "authenticate, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
public ICancellationSignal detectInteraction(int cookie) {
- return null;
+ Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+ return new ICancellationSignal.Stub() {
+ @Override
+ public void cancel() throws RemoteException {
+ cb.onError(Error.CANCELED, 0 /* vendorCode */);
+ }
+ };
}
@Override
- public void enumerateEnrollments(int cookie) {
-
+ public void enumerateEnrollments(int cookie) throws RemoteException {
+ Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsEnumerated(new int[0]);
}
@Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+ public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+ Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+ cb.onEnrollmentsRemoved(enrollmentIds);
}
@Override
- public void getAuthenticatorId(int cookie) {
-
+ public void getAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdRetrieved(0L);
}
@Override
- public void invalidateAuthenticatorId(int cookie) {
-
+ public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+ Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+ cb.onAuthenticatorIdInvalidated(0L);
}
@Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+ public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+ Slog.w(TAG, "resetLockout, cookie: " + cookie);
+ cb.onLockoutCleared();
}
@Override
public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
+ Slog.w(TAG, "onPointerDown");
}
@Override
public void onPointerUp(int pointerId) {
-
+ Slog.w(TAG, "onPointerUp");
}
@Override
public void onUiReady() {
-
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
+ Slog.w(TAG, "onUiReady");
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
deleted file mode 100644
index ac4f665..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-class TestSession extends ISession.Stub {
-
- private static final String TAG = "FingerprintTestSession";
-
- @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
-
- TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
- mHalSessionCallback = halSessionCallback;
- }
-
- @Override
- public void generateChallenge(int cookie, int timeoutSec) {
- mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
- }
-
- @Override
- public void revokeChallenge(int cookie, long challenge) {
- mHalSessionCallback.onChallengeRevoked(challenge);
- }
-
- @Override
- public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
- return null;
- }
-
- @Override
- public ICancellationSignal authenticate(int cookie, long operationId) {
- return new ICancellationSignal() {
- @Override
- public void cancel() {
- mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
- }
-
- @Override
- public IBinder asBinder() {
- return new Binder();
- }
- };
- }
-
- @Override
- public ICancellationSignal detectInteraction(int cookie) {
- return null;
- }
-
- @Override
- public void enumerateEnrollments(int cookie) {
- Slog.d(TAG, "enumerate");
- }
-
- @Override
- public void removeEnrollments(int cookie, int[] enrollmentIds) {
- Slog.d(TAG, "remove");
- }
-
- @Override
- public void getAuthenticatorId(int cookie) {
- Slog.d(TAG, "getAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdRetrieved(0);
- }
-
- @Override
- public void invalidateAuthenticatorId(int cookie) {
- Slog.d(TAG, "invalidateAuthenticatorId");
- // Immediately return a value so the framework can continue with subsequent requests.
- mHalSessionCallback.onAuthenticatorIdInvalidated(0);
- }
-
- @Override
- public void resetLockout(int cookie, HardwareAuthToken hat) {
- mHalSessionCallback.onLockoutCleared();
- }
-
- @Override
- public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
- }
-
- @Override
- public void onPointerUp(int pointerId) {
-
- }
-
- @Override
- public void onUiReady() {
-
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index b7aec0e..14fdb50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -21,11 +21,14 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
import android.os.RemoteException;
+import android.util.Slog;
/**
* Test HAL that provides only provides no-ops.
*/
public class TestHal extends IBiometricsFingerprint.Stub {
+ private static final String TAG = "fingerprint.hidl.TestHal";
+
@Nullable
private IBiometricsFingerprintClientCallback mCallback;
@@ -57,6 +60,7 @@
@Override
public int enroll(byte[] hat, int gid, int timeoutSec) {
+ Slog.w(TAG, "enroll");
return 0;
}
@@ -79,12 +83,18 @@
}
@Override
- public int enumerate() {
+ public int enumerate() throws RemoteException {
+ Slog.w(TAG, "Enumerate");
+ if (mCallback != null) {
+ mCallback.onEnumerate(0 /* deviceId */, 0 /* fingerId */, 0 /* groupId */,
+ 0 /* remaining */);
+ }
return 0;
}
@Override
public int remove(int gid, int fid) {
+ Slog.w(TAG, "Remove");
return 0;
}
@@ -95,6 +105,7 @@
@Override
public int authenticate(long operationId, int gid) {
+ Slog.w(TAG, "Authenticate");
return 0;
}
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 3d71b0a..6f112d7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -161,13 +161,20 @@
if (nai != null) {
transportType = approximateTransportType(nai);
final String extraInfo = nai.networkInfo.getExtraInfo();
- name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo;
+ if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null
+ && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
+ .getVenueFriendlyName())) {
+ name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+ } else {
+ name = TextUtils.isEmpty(extraInfo)
+ ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+ }
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
} else {
// Legacy notifications.
transportType = TRANSPORT_CELLULAR;
- name = null;
+ name = "";
}
// Clear any previous notification with lower priority, otherwise return. http://b/63676954.
@@ -193,35 +200,30 @@
final CharSequence details;
int icon = getIcon(transportType);
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.wifi_no_internet,
- WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+ title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
} else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
if (transportType == TRANSPORT_CELLULAR) {
title = r.getString(R.string.mobile_no_internet);
} else if (transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.wifi_no_internet,
- WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+ title = r.getString(R.string.wifi_no_internet, name);
} else {
title = r.getString(R.string.other_networks_no_internet);
}
details = r.getString(R.string.private_dns_broken_detailed);
} else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
&& transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.network_partial_connectivity,
- WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+ title = r.getString(R.string.network_partial_connectivity, name);
details = r.getString(R.string.network_partial_connectivity_detailed);
} else if (notifyType == NotificationType.LOST_INTERNET &&
transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.wifi_no_internet,
- WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+ title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
} else if (notifyType == NotificationType.SIGN_IN) {
switch (transportType) {
case TRANSPORT_WIFI:
title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+ details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
case TRANSPORT_CELLULAR:
title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 87b4c16..7ef315c 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,7 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.telephony.data.EpsBearerQosSessionAttributes;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.util.CollectionUtils;
import com.android.server.ConnectivityService;
@@ -260,18 +260,18 @@
}
private static void log(final String msg) {
- Slog.d(TAG, msg);
+ Log.d(TAG, msg);
}
private static void logw(final String msg) {
- Slog.w(TAG, msg);
+ Log.w(TAG, msg);
}
private static void loge(final String msg) {
- Slog.e(TAG, msg);
+ Log.e(TAG, msg);
}
private static void logwtf(final String msg) {
- Slog.wtf(TAG, msg);
+ Log.wtf(TAG, msg);
}
}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index b5f20d7..c480594 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -41,7 +41,6 @@
import android.os.MessageQueue;
import android.os.Messenger;
import android.system.ErrnoException;
-import android.system.Int32Ref;
import android.system.Os;
import android.util.Log;
import android.util.SparseArray;
@@ -306,9 +305,8 @@
private static boolean isReceiveQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- Int32Ref result = new Int32Ref(-1);
- Os.ioctlInt(fd, SIOCINQ, result);
- if (result.value != 0) {
+ final int result = Os.ioctlInt(fd, SIOCINQ);
+ if (result != 0) {
Log.e(TAG, "Read queue has data");
return false;
}
@@ -317,9 +315,8 @@
private static boolean isSendQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- Int32Ref result = new Int32Ref(-1);
- Os.ioctlInt(fd, SIOCOUTQ, result);
- if (result.value != 0) {
+ final int result = Os.ioctlInt(fd, SIOCOUTQ);
+ if (result != 0) {
Log.e(TAG, "Write queue has data");
return false;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index e496d77..e693bcc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -16,9 +16,14 @@
package com.android.server.devicestate;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
import android.annotation.IntRange;
import android.annotation.NonNull;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
@@ -35,24 +40,25 @@
*/
public final class DeviceState {
/** Unique identifier for the device state. */
- @IntRange(from = 0)
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
private final int mIdentifier;
/** String description of the device state. */
@NonNull
private final String mName;
- public DeviceState(@IntRange(from = 0) int identifier,
+ public DeviceState(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
@NonNull String name) {
- if (identifier < 0) {
- throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
- }
+ Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
+ "identifier");
+
mIdentifier = identifier;
mName = name;
}
/** Returns the unique identifier for the device state. */
- @IntRange(from = 0)
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
public int getIdentifier() {
return mIdentifier;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 984a176..b3a6f26 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
import android.annotation.IntRange;
@@ -89,7 +91,7 @@
// the current state after the initial callback from the DeviceStateProvider.
@GuardedBy("mLock")
@NonNull
- private DeviceState mCommittedState = new DeviceState(0, "UNSET");
+ private DeviceState mCommittedState = new DeviceState(MINIMUM_DEVICE_STATE, "UNSET");
// The device state that is currently awaiting callback from the policy to be committed.
@GuardedBy("mLock")
@NonNull
@@ -598,8 +600,9 @@
}
@Override
- public void onStateChanged(@IntRange(from = 0) int identifier) {
- if (identifier < 0) {
+ public void onStateChanged(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) {
+ if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 2d4377f..109bf63 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,6 +16,9 @@
package com.android.server.devicestate;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
import android.annotation.IntRange;
/**
@@ -65,8 +68,10 @@
*
* @param identifier the identifier of the new device state.
*
- * @throws IllegalArgumentException if the state is less than 0.
+ * @throws IllegalArgumentException if the state is less than {@link MINIMUM_DEVICE_STATE}
+ * or greater than {@link MAXIMUM_DEVICE_STATE}.
*/
- void onStateChanged(@IntRange(from = 0) int identifier);
+ void onStateChanged(
+ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier);
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
import android.util.Slog;
import android.util.TimeUtils;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
import android.util.Slog;
import android.util.Spline;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.server.display.utils.Plog;
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 0000000..d4556ed
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+ private static final String TAG = "DeviceStateToLayoutMap";
+
+ public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+ // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+ public static final int STATE_FOLDED = 100;
+ public static final int STATE_UNFOLDED = 101;
+
+ private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+ DeviceStateToLayoutMap(Context context) {
+ mLayoutMap.append(STATE_DEFAULT, new Layout());
+ loadFoldedDisplayConfig(context);
+ }
+
+ public void dumpLocked(IndentingPrintWriter ipw) {
+ ipw.println("DeviceStateToLayoutMap:");
+ ipw.increaseIndent();
+
+ ipw.println("Registered Layouts:");
+ for (int i = 0; i < mLayoutMap.size(); i++) {
+ ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+ }
+ }
+
+ Layout get(int state) {
+ Layout layout = mLayoutMap.get(state);
+ if (layout == null) {
+ layout = mLayoutMap.get(STATE_DEFAULT);
+ }
+ return layout;
+ }
+
+ private Layout create(int state) {
+ if (mLayoutMap.contains(state)) {
+ Slog.e(TAG, "Attempted to create a second layout for state " + state);
+ return null;
+ }
+
+ final Layout layout = new Layout();
+ mLayoutMap.append(state, layout);
+ return layout;
+ }
+
+ private void loadFoldedDisplayConfig(Context context) {
+ final String[] strDisplayIds = context.getResources().getStringArray(
+ com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+
+ if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
+ || TextUtils.isEmpty(strDisplayIds[1])) {
+ Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+ + "]");
+ return;
+ }
+
+ final long[] displayIds;
+ try {
+ displayIds = new long[] {
+ Long.parseLong(strDisplayIds[0]),
+ Long.parseLong(strDisplayIds[1])
+ };
+ } catch (NumberFormatException nfe) {
+ Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
+ return;
+ }
+
+ final int[] foldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ // Only add folded states if folded state config is not empty
+ if (foldedDeviceStates.length == 0) {
+ return;
+ }
+
+ // Create the folded state layout
+ final Layout foldedLayout = create(STATE_FOLDED);
+ foldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+ // Create the unfolded state layout
+ final Layout unfoldedLayout = create(STATE_UNFOLDED);
+ unfoldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
import android.util.Slog;
import android.view.DisplayAddress;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
import android.view.RoundedCorners;
import android.view.Surface;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import java.util.Arrays;
import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb..57f4486 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayAddress;
import com.android.internal.annotations.GuardedBy;
import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@
}
}
- public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- final DisplayDevice d = mDisplayDevices.get(i);
- if (uniqueId.equals(d.getUniqueId())) {
- return d;
+ public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+ for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+ final DisplayDevice device = mDisplayDevices.get(i);
+ if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+ return device;
}
}
return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..6a22941 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
import android.view.Surface;
import android.view.SurfaceControl;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
@@ -1188,6 +1188,7 @@
final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
final int state;
final int displayId = display.getDisplayIdLocked();
+
if (display.isEnabled()) {
state = mDisplayStates.get(displayId);
} else {
@@ -1564,12 +1565,6 @@
}
}
- void setFoldOverride(Boolean isFolded) {
- synchronized (mSyncRoot) {
- mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
- }
- }
-
private void clearViewportsLocked() {
mViewports.clear();
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
- case "set-fold":
- return setFoldOverride();
default:
return handleDefaultCommands(cmd);
}
@@ -92,8 +90,6 @@
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
- pw.println(" set-fold [fold|unfold|reset]");
- pw.println(" Simulates the 'fold' state of a device. 'reset' for default behavior.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -165,26 +161,4 @@
mService.setAmbientColorTemperatureOverride(cct);
return 0;
}
-
- private int setFoldOverride() {
- String state = getNextArg();
- if (state == null) {
- getErrPrintWriter().println("Error: no parameter specified for set-fold");
- return 1;
- }
- final Boolean isFolded;
- if ("fold".equals(state)) {
- isFolded = true;
- } else if ("unfold".equals(state)) {
- isFolded = false;
- } else if ("reset".equals(state)) {
- isFolded = null;
- } else {
- getErrPrintWriter().println("Error: Invalid fold state request: " + state);
- return 1;
- }
-
- mService.setFoldOverride(isFolded);
- return 0;
- }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
import android.util.TimeUtils;
import android.view.Display;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
import android.view.Choreographer;
import android.view.Display;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
import android.view.RoundedCorners;
import android.view.SurfaceControl;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d2..20b133c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.Objects;
@@ -718,6 +719,7 @@
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);
+ pw.println("mIsEnabled=" + mIsEnabled);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
}
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ dumpLocked(new PrintWriter(sw));
+ return sw.toString();
+ }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b26..a054db5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
+import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -75,40 +75,25 @@
private final boolean mSingleDisplayDemoMode;
/**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code true}.
- */
- private String mDisplayIdToUseWhenFolded;
-
- /**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code false}.
- */
- private String mDisplayIdToUseWhenUnfolded;
-
- /** Overrides the folded state of the device. For use with ADB commands. */
- private Boolean mIsFoldedOverride;
-
- /** Saves the last device fold state. */
- private boolean mIsFolded;
-
- /**
* List of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
* TODO: multi-display - Move the aforementioned comment?
*/
private final SparseArray<LogicalDisplay> mLogicalDisplays =
new SparseArray<LogicalDisplay>();
- private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
- private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
/** A mapping from logical display id to display group. */
private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
private final DisplayDeviceRepository mDisplayDeviceRepo;
+ private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener;
private final int[] mFoldedDeviceStates;
+ private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ private Layout mCurrentLayout = null;
+ private boolean mIsFolded = false;
+
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
mListener = listener;
@@ -118,7 +103,7 @@
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
- loadFoldedDisplayConfig(context);
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
}
@Override
@@ -213,13 +198,12 @@
ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
- ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+ ipw.println("mCurrentLayout=" + mCurrentLayout);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
for (int i = 0; i < logicalDisplayCount; i++) {
int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@
ipw.decreaseIndent();
ipw.println();
}
+ mDeviceStateToLayoutMap.dumpLocked(ipw);
}
void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@
void setDeviceFoldedLocked(boolean isFolded) {
mIsFolded = isFolded;
- if (mIsFoldedOverride != null) {
- isFolded = mIsFoldedOverride.booleanValue();
+
+ // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+ final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+ : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+ if (DEBUG) {
+ Slog.d(TAG, "New device state: " + state);
}
- if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
- || mLogicalDisplays.size() < 2) {
- // Do nothing if this behavior is disabled or there are less than two displays.
+ final Layout layout = mDeviceStateToLayoutMap.get(state);
+ if (layout == null) {
+ return;
+ }
+ final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final DisplayDevice newDefaultDevice =
+ mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+ if (newDefaultDevice == null) {
return;
}
- final DisplayDevice deviceFolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
- final DisplayDevice deviceUnfolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
- if (deviceFolded == null || deviceUnfolded == null) {
- // If the expected devices for folding functionality are not present, return early.
- return;
- }
-
- // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
- final LogicalDisplay displayFolded = getLocked(deviceFolded);
- final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
- if (displayFolded == null || displayUnfolded == null) {
- // If the expected displays are not present, return early.
- return;
- }
-
- // Find out which display is currently default and which is disabled.
final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
- final LogicalDisplay disabledDisplay;
- if (defaultDisplay == displayFolded) {
- disabledDisplay = displayUnfolded;
- } else if (defaultDisplay == displayUnfolded) {
- disabledDisplay = displayFolded;
- } else {
- // If neither folded or unfolded displays are currently set to the default display, we
- // are in an unknown state and it's best to log the error and bail.
- Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
- + " configured displays were set up as the default display. Default: "
- + defaultDisplay.getDisplayInfoLocked() + ", ConfiguredDisplays: [ folded="
- + displayFolded.getDisplayInfoLocked() + ", unfolded="
- + displayUnfolded.getDisplayInfoLocked() + " ]");
+ mCurrentLayout = layout;
+
+ // If we're already set up accurately, return early
+ if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
return;
}
- if (isFolded == (defaultDisplay == displayFolded)) {
- // Nothing to do, already in the right state.
+ // We need to swap the default display's display-device with the one that is supposed
+ // to be the default in the new layout.
+ final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+ if (displayToSwap == null) {
+ Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+ + newDefaultDevice);
return;
}
-
- // Everything was checked and we need to swap, lets swap.
- displayFolded.swapDisplaysLocked(displayUnfolded);
+ defaultDisplay.swapDisplaysLocked(displayToSwap);
// We ensure that the non-default Display is always forced to be off. This was likely
// already done in a previous iteration, but we do it with each swap in case something in
// the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
defaultDisplay.setEnabled(true);
- disabledDisplay.setEnabled(false);
+ displayToSwap.setEnabled(false);
// Update the world
updateLogicalDisplaysLocked();
-
- if (DEBUG) {
- Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
- + defaultDisplay.getDisplayInfoLocked());
- }
- }
-
- void setFoldOverrideLocked(Boolean isFolded) {
- if (!Objects.equals(isFolded, mIsFoldedOverride)) {
- mIsFoldedOverride = isFolded;
- setDeviceFoldedLocked(mIsFolded);
- }
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@
return;
}
- final int displayId = assignDisplayIdLocked(isDefault);
+ final int displayId = Layout.assignDisplayIdLocked(isDefault);
final int layerStack = assignLayerStackLocked(displayId);
final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@
return;
}
- mLogicalDisplays.put(displayId, display);
+ // For foldable devices, we start the internal non-default displays as disabled.
+ // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+ // display-device addition.
+ if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+ && !isDefault) {
+ display.setEnabled(false);
+ }
+ mLogicalDisplays.put(displayId, display);
displayGroup.addDisplayLocked(display);
mDisplayIdToGroupMap.append(displayId, displayGroup);
@@ -375,6 +343,10 @@
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "New Display added: " + display);
+ }
}
/**
@@ -466,10 +438,6 @@
}
}
- private int assignDisplayIdLocked(boolean isDefault) {
- return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
- }
-
private int assignDisplayGroupIdLocked(boolean isDefault) {
return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
}
@@ -480,21 +448,6 @@
return displayId;
}
- private void loadFoldedDisplayConfig(Context context) {
- final String[] displayIds = context.getResources().getStringArray(
- com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
- if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
- || TextUtils.isEmpty(displayIds[1])) {
- Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
- + "]");
- return;
- }
-
- mDisplayIdToUseWhenFolded = displayIds[0];
- mDisplayIdToUseWhenUnfolded = displayIds[1];
- }
-
public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
import android.util.FloatProperty;
import android.view.Choreographer;
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
/**
* A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 0000000..18f39e6
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * 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.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+ private static final String TAG = "Layout";
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+ private final List<Display> mDisplays = new ArrayList<>(2);
+
+ /**
+ * @return The default display ID, or a new unique one to use.
+ */
+ public static int assignDisplayIdLocked(boolean isDefault) {
+ return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+ }
+
+ @Override
+ public String toString() {
+ return mDisplays.toString();
+ }
+
+ /**
+ * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+ *
+ * @param address Address of the device.
+ * @param isDefault Indicates if the device is meant to be the default display.
+ * @return The new layout.
+ */
+ public Display createDisplayLocked(
+ @NonNull DisplayAddress address, boolean isDefault) {
+ if (contains(address)) {
+ Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+ return null;
+ }
+
+ // See if we're dealing with the "default" display
+ if (isDefault && getById(DEFAULT_DISPLAY) != null) {
+ Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
+ isDefault = false;
+ }
+
+ // Assign a logical display ID and create the new display.
+ // Note that the logical display ID is saved into the layout, so when switching between
+ // different layouts, a logical display can be destroyed and later recreated with the
+ // same logical display ID.
+ final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+ final Display layout = new Display(address, logicalDisplayId);
+
+ mDisplays.add(layout);
+ return layout;
+ }
+
+ /**
+ * @param address The address to check.
+ *
+ * @return True if the specified address is used in this layout.
+ */
+ public boolean contains(@NonNull DisplayAddress address) {
+ final int size = mDisplays.size();
+ for (int i = 0; i < size; i++) {
+ if (address.equals(mDisplays.get(i).getAddress())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param id The display ID to check.
+ *
+ * @return The display corresponding to the specified display ID.
+ */
+ public Display getById(int id) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ Display layout = mDisplays.get(i);
+ if (id == layout.getLogicalDisplayId()) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param index The index of the display to return.
+ *
+ * @return the display at the specified index.
+ */
+ public Display getAt(int index) {
+ return mDisplays.get(index);
+ }
+
+ /**
+ * @return The number of displays defined for this layout.
+ */
+ public int size() {
+ return mDisplays.size();
+ }
+
+ /**
+ * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+ */
+ public static class Display {
+ private final DisplayAddress mAddress;
+ private final int mLogicalDisplayId;
+
+ Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+ mAddress = address;
+ mLogicalDisplayId = logicalDisplayId;
+ }
+
+ @Override
+ public String toString() {
+ return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+ }
+
+ public DisplayAddress getAddress() {
+ return mAddress;
+ }
+
+ public int getLogicalDisplayId() {
+ return mLogicalDisplayId;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
index 017f11c..d514aab 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -17,6 +17,8 @@
package com.android.server.graphics.fonts;
import android.annotation.NonNull;
+import android.graphics.FontListParser;
+import android.text.FontConfig;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -30,6 +32,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/* package */ class PersistentSystemFontConfig {
@@ -38,11 +42,13 @@
private static final String TAG_ROOT = "fontConfig";
private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
+ private static final String TAG_FAMILY = "family";
private static final String ATTR_VALUE = "value";
/* package */ static class Config {
public long lastModifiedDate;
public final Set<String> updatedFontDirs = new ArraySet<>();
+ public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>();
}
/**
@@ -72,6 +78,11 @@
case TAG_UPDATED_FONT_DIR:
out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
break;
+ case TAG_FAMILY:
+ // updatableFontMap is not ready here. We get the base file names by passing
+ // empty fontDir, and resolve font paths later.
+ out.fontFamilies.add(FontListParser.readFamily(
+ parser, "" /* fontDir */, null /* updatableFontMap */));
default:
Slog.w(TAG, "Skipping unknown tag: " + tag);
}
@@ -97,6 +108,13 @@
out.attribute(null, ATTR_VALUE, dir);
out.endTag(null, TAG_UPDATED_FONT_DIR);
}
+ List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+ for (int i = 0; i < fontFamilies.size(); i++) {
+ FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+ out.startTag(null, TAG_FAMILY);
+ FontListParser.writeFamily(out, fontFamily);
+ out.endTag(null, TAG_FAMILY);
+ }
out.endTag(null, TAG_ROOT);
out.endDocument();
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index dac94f6..45f2a38 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -31,6 +31,8 @@
import android.util.Base64;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
@@ -40,6 +42,7 @@
import java.io.IOException;
import java.security.SecureRandom;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -118,6 +121,12 @@
*/
private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
+ /**
+ * A mutable map containing mapping from font family name to {@link FontConfig.FontFamily}.
+ * The FontFamily entries only reference font files in {@link #mFontFileInfoMap}.
+ */
+ private final ArrayMap<String, FontConfig.FontFamily> mFontFamilyMap = new ArrayMap<>();
+
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
FsverityUtil fsverityUtil) {
this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
@@ -136,6 +145,7 @@
/* package */ void loadFontFileMap() {
mFontFileInfoMap.clear();
+ mFontFamilyMap.clear();
mLastModifiedDate = 0;
boolean success = false;
try {
@@ -168,6 +178,13 @@
FontFileInfo fontFileInfo = validateFontFile(files[0]);
addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
}
+ // Resolve font file paths.
+ List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+ for (int i = 0; i < fontFamilies.size(); i++) {
+ FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+ // Ignore failures as updated fonts may be obsoleted by system OTA update.
+ addFontFamily(fontFamily);
+ }
success = true;
} catch (Throwable t) {
// If something happened during loading system fonts, clear all contents in finally
@@ -177,6 +194,7 @@
// Delete all files just in case if we find a problematic file.
if (!success) {
mFontFileInfoMap.clear();
+ mFontFamilyMap.clear();
mLastModifiedDate = 0;
FileUtils.deleteContents(mFilesDir);
}
@@ -186,10 +204,11 @@
/* package */ void clearUpdates() throws SystemFontException {
mFontFileInfoMap.clear();
FileUtils.deleteContents(mFilesDir);
+ mFontFamilyMap.clear();
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
- PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+ PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -206,17 +225,29 @@
public void update(List<FontUpdateRequest> requests) throws SystemFontException {
// Backup the mapping for rollback.
ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
+ ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap);
long backupLastModifiedDate = mLastModifiedDate;
boolean success = false;
try {
for (FontUpdateRequest request : requests) {
- installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
+ switch (request.getType()) {
+ case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
+ installFontFile(
+ request.getFd().getFileDescriptor(), request.getSignature());
+ break;
+ case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
+ // TODO: define error code.
+ if (!addFontFamily(request.getFontFamily())) {
+ throw new IllegalArgumentException("Invalid font family");
+ }
+ break;
+ }
}
// Write config file.
mLastModifiedDate = Instant.now().getEpochSecond();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
- PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+ PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -234,6 +265,8 @@
if (!success) {
mFontFileInfoMap.clear();
mFontFileInfoMap.putAll(backupMap);
+ mFontFamilyMap.clear();
+ mFontFamilyMap.putAll(backupFamilies);
mLastModifiedDate = backupLastModifiedDate;
}
}
@@ -454,12 +487,52 @@
}
}
- private PersistentSystemFontConfig.Config getPersistentConfig() {
+ /**
+ * Adds a font family to {@link #mFontFamilyMap} and returns true on success.
+ *
+ * <p>This method only accepts adding or updating a font family with a name.
+ * This is to prevent bad font family update from removing glyphs from font fallback chains.
+ * Unnamed font families are used as other named font family's fallback fonts to guarantee a
+ * complete glyph coverage.
+ */
+ private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
+ if (fontFamily.getName() == null) {
+ Slog.e(TAG, "Name is null.");
+ return false;
+ }
+ FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
+ if (resolvedFontFamily == null) {
+ Slog.e(TAG, "Required fonts are not available");
+ return false;
+ }
+ mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
+ return true;
+ }
+
+ @Nullable
+ private FontConfig.FontFamily resolveFontFiles(FontConfig.FontFamily fontFamily) {
+ List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontFamily.getFontList().size());
+ List<FontConfig.Font> fontList = fontFamily.getFontList();
+ for (int i = 0; i < fontList.size(); i++) {
+ FontConfig.Font font = fontList.get(i);
+ FontFileInfo info = mFontFileInfoMap.get(font.getFile().getName());
+ if (info == null) {
+ return null;
+ }
+ resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getStyle(),
+ font.getTtcIndex(), font.getFontVariationSettings(), font.getFontFamilyName()));
+ }
+ return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+ fontFamily.getLocaleList(), fontFamily.getVariant());
+ }
+
+ private PersistentSystemFontConfig.Config createPersistentConfig() {
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = mLastModifiedDate;
for (FontFileInfo info : mFontFileInfoMap.values()) {
config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
}
+ config.fontFamilies.addAll(mFontFamilyMap.values());
return config;
}
@@ -471,8 +544,24 @@
return map;
}
+ @VisibleForTesting
+ Map<String, FontConfig.FontFamily> getFontFamilyMap() {
+ return mFontFamilyMap;
+ }
+
/* package */ FontConfig getSystemFontConfig() {
- return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
+ FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0);
+ List<FontConfig.FontFamily> mergedFamilies =
+ new ArrayList<>(config.getFontFamilies().size() + mFontFamilyMap.size());
+ // We should keep the first font family (config.getFontFamilies().get(0)) because it's used
+ // as a fallback font. See SystemFonts.java.
+ mergedFamilies.addAll(config.getFontFamilies());
+ // When building Typeface, a latter font family definition will override the previous font
+ // family definition with the same name. An exception is config.getFontFamilies.get(0),
+ // which will be used as a fallback font without being overridden.
+ mergedFamilies.addAll(mFontFamilyMap.values());
+ return new FontConfig(
+ mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion);
}
/* package */ int getConfigVersion() {
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 909fcda..66fc0d9 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -17,6 +17,7 @@
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.util.SparseIntArray;
@@ -108,7 +109,10 @@
private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
mPowerStatus.clear();
for (HdmiDeviceInfo info : deviceInfos) {
- mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+ if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+ || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+ }
}
}
@@ -117,19 +121,22 @@
localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
resetPowerStatus(deviceInfos);
for (HdmiDeviceInfo info : deviceInfos) {
- final int logicalAddress = info.getLogicalAddress();
- sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
- logicalAddress),
- new SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- // If fails to send <Give Device Power Status>,
- // update power status into UNKNOWN.
- if (error != SendMessageResult.SUCCESS) {
- updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+ if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+ || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ final int logicalAddress = info.getLogicalAddress();
+ sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+ logicalAddress),
+ new SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int error) {
+ // If fails to send <Give Device Power Status>,
+ // update power status into UNKNOWN.
+ if (error != SendMessageResult.SUCCESS) {
+ updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+ }
}
- }
- });
+ });
+ }
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..0754df0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -178,6 +178,7 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -5229,15 +5230,7 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- boolean asProto = false;
- for (int argIndex = 0; argIndex < args.length; argIndex++) {
- if (args[argIndex].equals(PROTO_ARG)) {
- asProto = true;
- break;
- }
- }
-
- if (asProto) {
+ if (ArrayUtils.contains(args, PROTO_ARG)) {
final ImeTracing imeTracing = ImeTracing.getInstance();
if (imeTracing.isEnabled()) {
imeTracing.stopTrace(null, false /* writeToFile */);
@@ -5246,12 +5239,7 @@
imeTracing.startTrace(null);
});
}
- }
- doDump(fd, pw, args, asProto);
- }
- private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
- if (useProto) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
proto.flush();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 28dc516..47cb43e 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1253,7 +1253,8 @@
ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
mEventLog.copyAggregateStats();
for (int i = 0; i < aggregateStats.size(); i++) {
- ipw.println(aggregateStats.keyAt(i));
+ ipw.print(aggregateStats.keyAt(i));
+ ipw.println(":");
ipw.increaseIndent();
ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
aggregateStats.valueAt(i);
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 67060fc..865d41f 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -164,6 +164,7 @@
if (Build.IS_DEBUGGABLE || D) {
addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
}
+ getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
}
/** Logs that the location power save mode has changed. */
@@ -397,6 +398,8 @@
private int mActiveRequestCount;
@GuardedBy("this")
private int mForegroundRequestCount;
+ @GuardedBy("this")
+ private int mDeliveredLocationCount;
@GuardedBy("this")
private long mFastestIntervalMs = Long.MAX_VALUE;
@@ -464,6 +467,10 @@
Preconditions.checkState(mForegroundRequestCount >= 0);
}
+ synchronized void markLocationDelivered() {
+ mDeliveredLocationCount++;
+ }
+
public synchronized void updateTotals() {
if (mAddedRequestCount > 0) {
long realtimeMs = SystemClock.elapsedRealtime();
@@ -488,7 +495,8 @@
+ intervalToString(mSlowestIntervalMs)
+ ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
+ "/" + formatDuration(mActiveTimeTotalMs) + "/"
- + formatDuration(mForegroundTimeTotalMs);
+ + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+ + mDeliveredLocationCount;
}
private static String intervalToString(long intervalMs) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..685e9e6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -279,6 +279,7 @@
super.onBootPhase(phase);
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mLockSettingsService.migrateOldDataAfterSystemReady();
+ mLockSettingsService.loadEscrowData();
}
}
@@ -824,13 +825,17 @@
mSpManager.initWeaverService();
getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady();
- mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
mInjector.getFaceManager());
}
+ private void loadEscrowData() {
+ mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
+ }
+
private void getAuthSecretHal() {
try {
mAuthSecretService = IAuthSecret.getService(/* retry */ true);
@@ -2372,10 +2377,17 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
+ final int origPid = Binder.getCallingPid();
+ final int origUid = Binder.getCallingUid();
+
+ // The original identity is an opaque integer.
final long origId = Binder.clearCallingIdentity();
+ Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
try {
- (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec(
- this, in, out, err, args, callback, resultReceiver);
+ final LockSettingsShellCommand command =
+ new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
+ origUid);
+ command.exec(this, in, out, err, args, callback, resultReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 834cf05..67fae05 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -21,8 +21,11 @@
import android.app.ActivityManager;
import android.app.admin.PasswordMetrics;
+import android.content.Context;
import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.Slog;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -43,15 +46,25 @@
private static final String COMMAND_VERIFY = "verify";
private static final String COMMAND_GET_DISABLED = "get-disabled";
private static final String COMMAND_REMOVE_CACHE = "remove-cache";
+ private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE =
+ "set-resume-on-reboot-provider-package";
private static final String COMMAND_HELP = "help";
private int mCurrentUserId;
private final LockPatternUtils mLockPatternUtils;
+ private final Context mContext;
+ private final int mCallingPid;
+ private final int mCallingUid;
+
private String mOld = "";
private String mNew = "";
- LockSettingsShellCommand(LockPatternUtils lockPatternUtils) {
+ LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid,
+ int callingUid) {
mLockPatternUtils = lockPatternUtils;
+ mCallingPid = callingPid;
+ mCallingUid = callingUid;
+ mContext = context;
}
@Override
@@ -68,6 +81,7 @@
case COMMAND_HELP:
case COMMAND_GET_DISABLED:
case COMMAND_SET_DISABLED:
+ case COMMAND_SET_ROR_PROVIDER_PACKAGE:
break;
default:
getErrPrintWriter().println(
@@ -80,6 +94,9 @@
case COMMAND_REMOVE_CACHE:
runRemoveCache();
return 0;
+ case COMMAND_SET_ROR_PROVIDER_PACKAGE:
+ runSetResumeOnRebootProviderPackage();
+ return 0;
case COMMAND_HELP:
onHelp();
return 0;
@@ -170,6 +187,9 @@
pw.println("");
pw.println(" remove-cache [--user USER_ID]");
pw.println(" Removes cached unified challenge for the managed profile.");
+ pw.println(" set-resume-on-reboot-provider-package <package_name>");
+ pw.println(" Sets the package name for server based resume on reboot service "
+ + "provider.");
pw.println("");
}
}
@@ -256,6 +276,17 @@
return true;
}
+ private boolean runSetResumeOnRebootProviderPackage() {
+ final String packageName = mNew;
+ String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE;
+ Slog.i(TAG, "Setting " + name + " to " + packageName);
+
+ mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE,
+ mCallingPid, mCallingUid, TAG);
+ SystemProperties.set(name, packageName);
+ return true;
+ }
+
private boolean runClear() {
LockscreenCredential none = LockscreenCredential.createNone();
if (!isNewCredentialSufficient(none)) {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 06962d4..53b62ca 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.os.Handler;
import android.os.SystemClock;
import android.os.UserManager;
import android.provider.DeviceConfig;
@@ -39,6 +40,7 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import javax.crypto.SecretKey;
@@ -76,6 +78,13 @@
private static final int BOOT_COUNT_TOLERANCE = 5;
/**
+ * The default retry specs for loading reboot escrow data. We will attempt to retry loading
+ * escrow data on temporarily errors, e.g. unavailable network.
+ */
+ private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
+ private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+
+ /**
* Logs events for later debugging in bugreports.
*/
private final RebootEscrowEventLog mEventLog;
@@ -148,6 +157,14 @@
return null;
}
+ void post(Handler handler, Runnable runnable) {
+ handler.post(runnable);
+ }
+
+ void postDelayed(Handler handler, Runnable runnable, long delayMillis) {
+ handler.postDelayed(runnable, delayMillis);
+ }
+
public Context getContext() {
return mContext;
}
@@ -199,7 +216,18 @@
mKeyStoreManager = injector.getKeyStoreManager();
}
- void loadRebootEscrowDataIfAvailable() {
+ private void onGetRebootEscrowKeyFailed(List<UserInfo> users) {
+ Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+
+ // Clear the old key in keystore.
+ mKeyStoreManager.clearKeyStoreEncryptionKey();
+ onEscrowRestoreComplete(false);
+ }
+
+ void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
List<UserInfo> users = mUserManager.getUsers();
List<UserInfo> rebootEscrowUsers = new ArrayList<>();
for (UserInfo user : users) {
@@ -212,17 +240,49 @@
return;
}
+ mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
+ retryHandler, 0, users, rebootEscrowUsers));
+ }
+
+ void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber,
+ List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+ Objects.requireNonNull(retryHandler);
+
+ final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
+ final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+ "load_escrow_data_retry_interval_seconds",
+ DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+
+ if (attemptNumber < retryLimit) {
+ Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
+ mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry(
+ retryHandler, attemptNumber, users, rebootEscrowUsers),
+ retryIntervalInSeconds * 1000);
+ return;
+ }
+
+ Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+ onGetRebootEscrowKeyFailed(users);
+ }
+
+ void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber,
+ List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
// Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
// generated before reboot. Note that we will clear the escrow key even if the keystore key
// is null.
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
- RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
+ RebootEscrowKey escrowKey;
+ try {
+ escrowKey = getAndClearRebootEscrowKey(kk);
+ } catch (IOException e) {
+ scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
+ rebootEscrowUsers);
+ return;
+ }
+
if (kk == null || escrowKey == null) {
- Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
- for (UserInfo user : users) {
- mStorage.removeRebootEscrow(user.id);
- }
- onEscrowRestoreComplete(false);
+ onGetRebootEscrowKeyFailed(users);
return;
}
@@ -249,7 +309,7 @@
}
}
- private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
+ private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
if (rebootEscrowProvider == null) {
Slog.w(TAG,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 6c1040b..4b00772 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -33,7 +33,7 @@
* An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
*/
class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
- private static final String TAG = "RebootEscrowProvider";
+ private static final String TAG = "RebootEscrowProviderHal";
private final Injector mInjector;
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index 857ad5f..af6faad 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,6 +16,8 @@
package com.android.server.locksettings;
+import java.io.IOException;
+
import javax.crypto.SecretKey;
/**
@@ -33,9 +35,10 @@
/**
* Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted,
- * use the input key to decrypt the RebootEscrowKey. Returns null on failure.
+ * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an
+ * IOException if the failure is non-fatal, and a retry may succeed.
*/
- RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey);
+ RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException;
/**
* Clears the stored RebootEscrowKey.
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index ba1a680..9d09637 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -35,7 +35,7 @@
* encrypt & decrypt the blob.
*/
class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
- private static final String TAG = "RebootEscrowProvider";
+ private static final String TAG = "RebootEscrowProviderServerBased";
// Timeout for service binding
private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
@@ -50,6 +50,8 @@
private final Injector mInjector;
+ private byte[] mServerBlob;
+
static class Injector {
private ResumeOnRebootServiceConnection mServiceConnection = null;
@@ -124,17 +126,20 @@
}
@Override
- public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
- byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+ public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
+ if (mServerBlob == null) {
+ mServerBlob = mStorage.readRebootEscrowServerBlob();
+ }
// Delete the server blob in storage.
mStorage.removeRebootEscrowServerBlob();
- if (serverBlob == null) {
+ if (mServerBlob == null) {
Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
return null;
}
+ Slog.i(TAG, "Loaded reboot escrow server blob from storage");
try {
- byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+ byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
if (escrowKeyBytes == null) {
Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
return null;
@@ -145,7 +150,7 @@
}
return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
- } catch (TimeoutException | RemoteException | IOException e) {
+ } catch (TimeoutException | RemoteException e) {
Slog.w(TAG, "Failed to decrypt the server blob ", e);
return null;
}
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index a1e18bd..9c471b8 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -31,6 +31,7 @@
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.resumeonreboot.IResumeOnRebootService;
@@ -55,6 +56,10 @@
Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE;
private static final String TAG = "ResumeOnRebootServiceProvider";
+ // The system property name that overrides the default service provider package name.
+ static final String PROP_ROR_PROVIDER_PACKAGE =
+ "persist.sys.resume_on_reboot_provider_package";
+
private final Context mContext;
private final PackageManager mPackageManager;
@@ -72,12 +77,19 @@
private ServiceInfo resolveService() {
Intent intent = new Intent();
intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE);
- if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
- intent.setPackage(PROVIDER_PACKAGE);
+ int queryFlag = PackageManager.GET_SERVICES;
+ String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, "");
+ if (!testAppName.isEmpty()) {
+ Slog.i(TAG, "Using test app: " + testAppName);
+ intent.setPackage(testAppName);
+ } else {
+ queryFlag |= PackageManager.MATCH_SYSTEM_ONLY;
+ if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
+ intent.setPackage(PROVIDER_PACKAGE);
+ }
}
- List<ResolveInfo> resolvedIntents =
- mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+ List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag);
for (ResolveInfo resolvedInfo : resolvedIntents) {
if (resolvedInfo.serviceInfo != null
&& PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) {
@@ -120,6 +132,7 @@
if (mServiceConnection != null) {
mContext.unbindService(mServiceConnection);
}
+ mBinder = null;
}
/** Bind to the service */
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 74111be..c462a92 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -55,7 +55,9 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -120,8 +122,8 @@
private final Context mContext;
private final Object mLock = new Object();
- private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
- new ArrayList<>();
+ private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
+ mControllerCallbackHolders = new CopyOnWriteArrayList<>();
private long mFlags;
private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
@@ -545,51 +547,66 @@
}
private void pushPlaybackStateUpdate() {
+ PlaybackState playbackState;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onPlaybackStateChanged(mPlaybackState);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushPlaybackStateUpdate",
- holder, e);
+ playbackState = mPlaybackState;
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onPlaybackStateChanged(playbackState);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder,
+ e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushMetadataUpdate() {
+ MediaMetadata metadata;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onMetadataChanged(mMetadata);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+ metadata = mMetadata;
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onMetadataChanged(metadata);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushQueueUpdate() {
+ ParceledListSlice<QueueItem> parcelableQueue;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- ParceledListSlice<QueueItem> parcelableQueue;
if (mQueue == null) {
parcelableQueue = null;
} else {
@@ -598,77 +615,105 @@
// as onQueueChanged is an async binder call.
parcelableQueue.setInlineCountLimit(1);
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onQueueChanged(parcelableQueue);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onQueueChanged(parcelableQueue);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushQueueTitleUpdate() {
+ CharSequence queueTitle;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onQueueTitleChanged(mQueueTitle);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushQueueTitleUpdate",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
+ queueTitle = mQueueTitle;
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onQueueTitleChanged(queueTitle);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushExtrasUpdate() {
+ Bundle extras;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onExtrasChanged(mExtras);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+ extras = mExtras;
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onExtrasChanged(extras);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushVolumeUpdate() {
+ PlaybackInfo info;
synchronized (mLock) {
if (mDestroyed) {
return;
}
- PlaybackInfo info = getVolumeAttributes();
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onVolumeInfoChanged(info);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+ info = getVolumeAttributes();
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onVolumeInfoChanged(info);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushEvent(String event, Bundle data) {
@@ -676,18 +721,24 @@
if (mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onEvent(event, data);
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushEvent", holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushEvent", holder, e);
+ }
+ Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onEvent(event, data);
+ } catch (DeadObjectException e) {
+ if (deadCallbackHolders == null) {
+ deadCallbackHolders = new ArrayList<>();
}
+ deadCallbackHolders.add(holder);
+ logCallbackException("Removing dead callback in pushEvent", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushEvent", holder, e);
}
}
+ if (deadCallbackHolders != null) {
+ mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ }
}
private void pushSessionDestroyed() {
@@ -697,21 +748,18 @@
if (!mDestroyed) {
return;
}
- for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
- ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
- try {
- holder.mCallback.onSessionDestroyed();
- } catch (DeadObjectException e) {
- mControllerCallbackHolders.remove(i);
- logCallbackException("Removing dead callback in pushSessionDestroyed",
- holder, e);
- } catch (RemoteException e) {
- logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
- }
- }
- // After notifying clear all listeners
- mControllerCallbackHolders.clear();
}
+ for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+ try {
+ holder.mCallback.onSessionDestroyed();
+ } catch (DeadObjectException e) {
+ logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
+ } catch (RemoteException e) {
+ logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
+ }
+ }
+ // After notifying clear all listeners
+ mControllerCallbackHolders.clear();
}
private PlaybackState getStateWithUpdatedPosition() {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 769b781..78c1a95 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.IBinder;
import android.os.IInterface;
@@ -197,6 +198,11 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ // nothing to filter
+ }
+
+ @Override
protected void loadDefaultsFromConfig() {
String defaultDndAccess = mContext.getResources().getString(
R.string.config_defaultDndAccessPackages);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 21537e6..bbdcac2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -189,6 +189,8 @@
abstract protected void onServiceAdded(ManagedServiceInfo info);
+ abstract protected void ensureFilters(ServiceInfo si, int userId);
+
protected List<ManagedServiceInfo> getServices() {
synchronized (mMutex) {
List<ManagedServiceInfo> services = new ArrayList<>(mServices);
@@ -1342,8 +1344,10 @@
for (ComponentName component : add) {
try {
ServiceInfo info = mPm.getServiceInfo(component,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ userId);
if (info == null) {
Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+ ": service not found");
@@ -1356,7 +1360,7 @@
}
Slog.v(TAG,
"enabling " + getCaption() + " for " + userId + ": " + component);
- registerService(component, userId);
+ registerService(info, userId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -1368,9 +1372,15 @@
* Version of registerService that takes the name of a service component to bind to.
*/
@VisibleForTesting
- void registerService(final ComponentName name, final int userid) {
+ void registerService(final ServiceInfo si, final int userId) {
+ ensureFilters(si, userId);
+ registerService(si.getComponentName(), userId);
+ }
+
+ @VisibleForTesting
+ void registerService(final ComponentName cn, final int userId) {
synchronized (mMutex) {
- registerServiceLocked(name, userid);
+ registerServiceLocked(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..917be29 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,6 +110,8 @@
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -175,6 +177,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -212,6 +215,7 @@
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
@@ -284,7 +288,6 @@
import com.android.server.notification.toast.TextToastRecord;
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
-import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
@@ -358,7 +361,7 @@
private static final int MESSAGE_RECONSIDER_RANKING = 1000;
private static final int MESSAGE_RANKING_SORT = 1001;
- static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+ static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds
// 1 second past the ANR timeout.
@@ -3056,7 +3059,7 @@
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
- showNextToastLocked();
+ showNextToastLocked(false);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -7392,7 +7395,7 @@
}
@GuardedBy("mToastQueue")
- void showNextToastLocked() {
+ void showNextToastLocked(boolean lastToastWasTextRecord) {
if (mIsCurrentToastShown) {
return; // Don't show the same toast twice.
}
@@ -7406,7 +7409,7 @@
mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
- scheduleDurationReachedLocked(record);
+ scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if (rateLimitingEnabled) {
mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
@@ -7477,7 +7480,7 @@
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
- showNextToastLocked();
+ showNextToastLocked(lastToast instanceof TextToastRecord);
}
}
@@ -7491,7 +7494,7 @@
}
@GuardedBy("mToastQueue")
- private void scheduleDurationReachedLocked(ToastRecord r)
+ private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
@@ -7501,6 +7504,14 @@
// preference.
delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
AccessibilityManager.FLAG_CONTENT_TEXT);
+
+ if (lastToastWasTextRecord) {
+ delay += 250; // delay to account for previous toast's "out" animation
+ }
+ if (r instanceof TextToastRecord) {
+ delay += 333; // delay to account for this toast's "in" animation
+ }
+
mHandler.sendMessageDelayed(m, delay);
}
@@ -8960,7 +8971,8 @@
NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey);
if (nls != null
&& (!nls.isTypeAllowed(notificationType)
- || !nls.isPackageAllowed(sbn.getPackageName()))) {
+ || !nls.isPackageAllowed(
+ new VersionedPackage(sbn.getPackageName(), sbn.getUid())))) {
return false;
}
return true;
@@ -9123,6 +9135,12 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ // nothing to filter; no user visible settings for types/packages like other
+ // listeners
+ }
+
+ @Override
@GuardedBy("mNotificationLock")
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
mListeners.unregisterService(removed.service, removed.userid);
@@ -9567,11 +9585,12 @@
public class NotificationListeners extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
- static final String TAG_REQUESTED_LISTENERS = "req_listeners";
+ static final String TAG_REQUESTED_LISTENERS = "request_listeners";
static final String TAG_REQUESTED_LISTENER = "listener";
static final String ATT_COMPONENT = "component";
static final String ATT_TYPES = "types";
- static final String ATT_PKGS = "pkgs";
+ static final String ATT_PKG = "pkg";
+ static final String ATT_UID = "uid";
static final String TAG_APPROVED = "allowed";
static final String TAG_DISALLOWED= "disallowed";
static final String XML_SEPARATOR = ",";
@@ -9689,28 +9708,6 @@
}
@Override
- public void onUserUnlocked(int user) {
- int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
-
- final PackageManager pmWrapper = mContext.getPackageManager();
- List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
- new Intent(getConfig().serviceInterface), flags, user);
-
- for (ResolveInfo resolveInfo : installedServices) {
- ServiceInfo info = resolveInfo.serviceInfo;
-
- if (!getConfig().bindPermission.equals(info.permission)) {
- continue;
- }
- Pair key = Pair.create(info.getComponentName(), user);
- if (!mRequestedNotificationListeners.containsKey(key)) {
- mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
- }
- }
- super.onUserUnlocked(user);
- }
-
- @Override
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
super.onPackagesChanged(removingPackage, pkgList, uidList);
@@ -9729,6 +9726,18 @@
}
}
}
+
+ // clean up anything in the disallowed pkgs list
+ for (int i = 0; i < pkgList.length; i++) {
+ String pkg = pkgList[i];
+ int userId = UserHandle.getUserId(uidList[i]);
+ for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
+ NotificationListenerFilter nlf = mRequestedNotificationListeners.valueAt(j);
+
+ VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
+ nlf.removePackage(ai);
+ }
+ }
}
@Override
@@ -9758,15 +9767,17 @@
int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING;
- ArraySet<String> disallowedPkgs = new ArraySet<>();
+ ArraySet<VersionedPackage> disallowedPkgs = new ArraySet<>();
final int listenerOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) {
if (TAG_APPROVED.equals(parser.getName())) {
approved = XmlUtils.readIntAttribute(parser, ATT_TYPES);
} else if (TAG_DISALLOWED.equals(parser.getName())) {
- String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS);
- if (!TextUtils.isEmpty(pkgs)) {
- disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR));
+ String pkg = XmlUtils.readStringAttribute(parser, ATT_PKG);
+ int uid = XmlUtils.readIntAttribute(parser, ATT_UID);
+ if (!TextUtils.isEmpty(pkg)) {
+ VersionedPackage ai = new VersionedPackage(pkg, uid);
+ disallowedPkgs.add(ai);
}
}
}
@@ -9791,10 +9802,14 @@
XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes());
out.endTag(null, TAG_APPROVED);
- out.startTag(null, TAG_DISALLOWED);
- XmlUtils.writeStringAttribute(
- out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages()));
- out.endTag(null, TAG_DISALLOWED);
+ for (VersionedPackage ai : nlf.getDisallowedPackages()) {
+ if (!TextUtils.isEmpty(ai.getPackageName())) {
+ out.startTag(null, TAG_DISALLOWED);
+ XmlUtils.writeStringAttribute(out, ATT_PKG, ai.getPackageName());
+ XmlUtils.writeIntAttribute(out, ATT_UID, ai.getVersionCode());
+ out.endTag(null, TAG_DISALLOWED);
+ }
+ }
out.endTag(null, TAG_REQUESTED_LISTENER);
}
@@ -9812,6 +9827,38 @@
mRequestedNotificationListeners.put(pair, nlf);
}
+ @Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+ Pair listener = Pair.create(si.getComponentName(), userId);
+ NotificationListenerFilter existingNlf =
+ mRequestedNotificationListeners.get(listener);
+ if (existingNlf == null) {
+ // no stored filters for this listener; see if they provided a default
+ if (si.metaData != null) {
+ String typeList = si.metaData.getString(
+ NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES);
+ if (typeList != null) {
+ int types = 0;
+ String[] typeStrings = typeList.split(XML_SEPARATOR);
+ for (int i = 0; i < typeStrings.length; i++) {
+ if (TextUtils.isEmpty(typeStrings[i])) {
+ continue;
+ }
+ try {
+ types |= Integer.parseInt(typeStrings[i]);
+ } catch (NumberFormatException e) {
+ // skip
+ }
+ }
+
+ NotificationListenerFilter nlf =
+ new NotificationListenerFilter(types, new ArraySet<>());
+ mRequestedNotificationListeners.put(listener, nlf);
+ }
+ }
+ }
+ }
+
@GuardedBy("mNotificationLock")
public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
if (trim == TRIM_LIGHT) {
diff --git a/services/core/java/com/android/server/om/DumpState.java b/services/core/java/com/android/server/om/DumpState.java
index 1e2e054..88afcb2 100644
--- a/services/core/java/com/android/server/om/DumpState.java
+++ b/services/core/java/com/android/server/om/DumpState.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.om.OverlayIdentifier;
import android.os.UserHandle;
/**
@@ -26,6 +27,7 @@
public final class DumpState {
@UserIdInt private int mUserId = UserHandle.USER_ALL;
@Nullable private String mPackageName;
+ @Nullable private String mOverlayName;
@Nullable private String mField;
private boolean mVerbose;
@@ -38,14 +40,19 @@
}
/** Sets the name of the package to dump the state for */
- public void setPackageName(String packageName) {
- mPackageName = packageName;
+ public void setOverlyIdentifier(String overlayIdentifier) {
+ final OverlayIdentifier overlay = OverlayIdentifier.fromString(overlayIdentifier);
+ mPackageName = overlay.getPackageName();
+ mOverlayName = overlay.getOverlayName();
}
@Nullable public String getPackageName() {
return mPackageName;
}
+ @Nullable public String getOverlayName() {
+ return mOverlayName;
+ }
- /** Sets the name of the field to dump the state of */
+ /** Sets the name of the field to dump the state for */
public void setField(String field) {
mField = field;
}
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 19fa920..2ebc8ed 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -20,17 +20,23 @@
import static com.android.server.om.OverlayManagerService.TAG;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
import android.os.IBinder;
import android.os.IIdmap2;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemService;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.server.FgThread;
import java.io.File;
+import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -104,10 +110,12 @@
return sInstance;
}
- String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
- int userId) throws TimeoutException, RemoteException {
+ String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+ @Nullable String overlayName, int policies, boolean enforce, int userId)
+ throws TimeoutException, RemoteException {
try (Connection c = connect()) {
- return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
+ return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+ policies, enforce, userId);
}
}
@@ -117,11 +125,12 @@
}
}
- boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
- int userId)
+ boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+ @Nullable String overlayName, int policies, boolean enforce, int userId)
throws Exception {
try (Connection c = connect()) {
- return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId);
+ return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+ policies, enforce, userId);
}
}
@@ -129,12 +138,38 @@
try (Connection c = connect()) {
return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
} catch (Exception e) {
- Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
- + e.getMessage());
+ Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
return false;
}
}
+ FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+ try (Connection c = connect()) {
+ return mService.createFabricatedOverlay(overlay);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
+ return null;
+ }
+ }
+
+ boolean deleteFabricatedOverlay(@NonNull String path) {
+ try (Connection c = connect()) {
+ return mService.deleteFabricatedOverlay(path);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
+ return false;
+ }
+ }
+
+ List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+ try (Connection c = connect()) {
+ return mService.getFabricatedOverlayInfos();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to get fabricated overlays", e);
+ return null;
+ }
+ }
+
private IBinder getIdmapService() throws TimeoutException, RemoteException {
SystemService.start(IDMAP_DAEMON);
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index eeb2655..64362c9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -22,15 +22,19 @@
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser;
import android.os.Build.VERSION_CODES;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
import android.os.OverlayablePolicy;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
import java.io.IOException;
+import java.util.List;
/**
* Handle the creation and deletion of idmap files.
@@ -74,25 +78,26 @@
* Creates the idmap for the target/overlay combination and returns whether the idmap file was
* modified.
*/
- boolean createIdmap(@NonNull final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, int userId) {
+ boolean createIdmap(@NonNull final AndroidPackage targetPackage,
+ @NonNull final AndroidPackage overlayPackage, String overlayBasePath,
+ String overlayName, int userId) {
if (DEBUG) {
- Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
- + overlayPackage.packageName);
+ Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
+ + overlayPackage.getPackageName());
}
- final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
- final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
+ final String targetPath = targetPackage.getBaseApkPath();
try {
int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
boolean enforce = enforceOverlayable(overlayPackage);
- if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
+ if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
+ enforce, userId)) {
return false;
}
- return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+ return mIdmapDaemon.createIdmap(targetPath, overlayBasePath, overlayName, policies,
enforce, userId) != null;
} catch (Exception e) {
Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
- + overlayPath + ": " + e.getMessage());
+ + overlayBasePath, e);
return false;
}
}
@@ -104,7 +109,7 @@
try {
return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
} catch (Exception e) {
- Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
+ Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath, e);
return false;
}
}
@@ -113,22 +118,40 @@
return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
}
- boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
+ /**
+ * @return the list of all fabricated overlays
+ */
+ List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+ return mIdmapDaemon.getFabricatedOverlayInfos();
+ }
+
+ /**
+ * Creates a fabricated overlay and persists it to disk.
+ * @return the path to the fabricated overlay
+ */
+ FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+ return mIdmapDaemon.createFabricatedOverlay(overlay);
+ }
+
+ /**
+ * Deletes the fabricated overlay file on disk.
+ * @return whether the path was deleted
+ */
+ boolean deleteFabricatedOverlay(@NonNull String path) {
+ return mIdmapDaemon.deleteFabricatedOverlay(path);
}
/**
* Checks if overlayable and policies should be enforced on the specified overlay for backwards
* compatibility with pre-Q overlays.
*/
- private boolean enforceOverlayable(@NonNull final PackageInfo overlayPackage) {
- final ApplicationInfo ai = overlayPackage.applicationInfo;
- if (ai.targetSdkVersion >= VERSION_CODES.Q) {
+ private boolean enforceOverlayable(@NonNull final AndroidPackage overlayPackage) {
+ if (overlayPackage.getTargetSdkVersion() >= VERSION_CODES.Q) {
// Always enforce policies for overlays targeting Q+.
return true;
}
- if (ai.isVendor()) {
+ if (overlayPackage.isVendor()) {
// If the overlay is on a pre-Q vendor partition, do not enforce overlayable
// restrictions on this overlay because the pre-Q platform has no understanding of
// overlayable.
@@ -137,20 +160,19 @@
// Do not enforce overlayable restrictions on pre-Q overlays that are signed with the
// platform signature or that are preinstalled.
- return !(ai.isSystemApp() || ai.isSignedWithPlatformKey());
+ return !(overlayPackage.isSystem() || overlayPackage.isSignedWithPlatformKey());
}
/**
* Retrieves a bitmask for idmap2 that represents the policies the overlay fulfills.
*/
- private int calculateFulfilledPolicies(@NonNull final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, int userId) {
- final ApplicationInfo ai = overlayPackage.applicationInfo;
+ private int calculateFulfilledPolicies(@NonNull final AndroidPackage targetPackage,
+ @NonNull final AndroidPackage overlayPackage, int userId) {
int fulfilledPolicies = OverlayablePolicy.PUBLIC;
// Overlay matches target signature
- if (mPackageManager.signaturesMatching(targetPackage.packageName,
- overlayPackage.packageName, userId)) {
+ if (mPackageManager.signaturesMatching(targetPackage.getPackageName(),
+ overlayPackage.getPackageName(), userId)) {
fulfilledPolicies |= OverlayablePolicy.SIGNATURE;
}
@@ -164,52 +186,52 @@
// preinstalled package, check if overlay matches its signature.
if (!TextUtils.isEmpty(mConfigSignaturePackage)
&& mPackageManager.signaturesMatching(mConfigSignaturePackage,
- overlayPackage.packageName,
+ overlayPackage.getPackageName(),
userId)) {
fulfilledPolicies |= OverlayablePolicy.CONFIG_SIGNATURE;
}
// Vendor partition (/vendor)
- if (ai.isVendor()) {
+ if (overlayPackage.isVendor()) {
return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
}
// Product partition (/product)
- if (ai.isProduct()) {
+ if (overlayPackage.isProduct()) {
return fulfilledPolicies | OverlayablePolicy.PRODUCT_PARTITION;
}
// Odm partition (/odm)
- if (ai.isOdm()) {
+ if (overlayPackage.isOdm()) {
return fulfilledPolicies | OverlayablePolicy.ODM_PARTITION;
}
// Oem partition (/oem)
- if (ai.isOem()) {
+ if (overlayPackage.isOem()) {
return fulfilledPolicies | OverlayablePolicy.OEM_PARTITION;
}
// System_ext partition (/system_ext) is considered as system
// Check this last since every partition except for data is scanned as system in the PMS.
- if (ai.isSystemApp() || ai.isSystemExt()) {
+ if (overlayPackage.isSystem() || overlayPackage.isSystemExt()) {
return fulfilledPolicies | OverlayablePolicy.SYSTEM_PARTITION;
}
return fulfilledPolicies;
}
- private boolean matchesActorSignature(@NonNull PackageInfo targetPackage,
- @NonNull PackageInfo overlayPackage, int userId) {
- String targetOverlayableName = overlayPackage.targetOverlayableName;
+ private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
+ @NonNull AndroidPackage overlayPackage, int userId) {
+ String targetOverlayableName = overlayPackage.getOverlayTargetName();
if (targetOverlayableName != null) {
try {
OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
- targetPackage.packageName, targetOverlayableName, userId);
+ targetPackage.getPackageName(), targetOverlayableName, userId);
if (overlayableInfo != null && overlayableInfo.actor != null) {
String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
overlayableInfo.actor, mPackageManager.getNamedActors()).first;
if (mPackageManager.signaturesMatching(actorPackageName,
- overlayPackage.packageName, userId)) {
+ overlayPackage.getPackageName(), userId)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 8121a43e9..2d540de 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,8 +19,6 @@
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
@@ -29,6 +27,7 @@
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 java.io.IOException;
import java.util.List;
@@ -114,12 +113,13 @@
}
final String targetPackageName = overlayInfo.targetPackageName;
- final PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
+ final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
+ userId);
if (targetPkgInfo == null) {
return ActorState.TARGET_NOT_FOUND;
}
- if ((targetPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ if (targetPkgInfo.isDebuggable()) {
return ActorState.ALLOWED;
}
@@ -189,23 +189,18 @@
return actorUriState;
}
- String packageName = actorUriPair.first;
- PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, userId);
- if (packageInfo == null) {
- return ActorState.MISSING_APP_INFO;
- }
-
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- if (appInfo == null) {
- return ActorState.MISSING_APP_INFO;
+ String actorPackageName = actorUriPair.first;
+ AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
+ if (actorPackage == null) {
+ return ActorState.ACTOR_NOT_FOUND;
}
// Currently only pre-installed apps can be actors
- if (!appInfo.isSystemApp()) {
+ if (!actorPackage.isSystem()) {
return ActorState.ACTOR_NOT_PREINSTALLED;
}
- if (ArrayUtils.contains(callingPackageNames, packageName)) {
+ if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
return ActorState.ALLOWED;
}
@@ -231,7 +226,7 @@
NO_NAMED_ACTORS,
MISSING_NAMESPACE,
MISSING_ACTOR_NAME,
- MISSING_APP_INFO,
+ ACTOR_NOT_FOUND,
ACTOR_NOT_PREINSTALLED,
INVALID_ACTOR,
ALLOWED
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index fd2fb1f..905733c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -24,8 +24,10 @@
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_REASON;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
@@ -43,11 +45,11 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayManagerTransaction;
import android.content.om.OverlayableInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.pm.overlay.OverlayPaths;
@@ -55,8 +57,10 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.FabricatedOverlayInternal;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -71,12 +75,15 @@
import android.util.SparseArray;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
+
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import libcore.util.EmptyArray;
@@ -92,13 +99,12 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.function.Consumer;
/**
* Service to manage asset overlays.
@@ -247,15 +253,6 @@
private final OverlayActorEnforcer mActorEnforcer;
- private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
- persistSettings();
- FgThread.getHandler().post(() -> {
- List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
- updateActivityManager(affectedTargets, pair.userId);
- broadcastActionOverlayChanged(pair.packageName, pair.userId);
- });
- };
-
public OverlayManagerService(@NonNull final Context context) {
super(context);
try {
@@ -315,8 +312,7 @@
// Initialize any users that can't be switched to, as their state would
// never be setup in onSwitchUser(). We will switch to the system user right
// after this, and its state will be setup there.
- final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
- updatePackageManager(targets, users.get(i).id);
+ updatePackageManager(mImpl.updateOverlaysForUser(users.get(i).id));
}
}
}
@@ -333,9 +329,7 @@
// ensure overlays in the settings are up-to-date, and propagate
// any asset changes to the rest of the system
synchronized (mLock) {
- final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
- final List<String> affectedTargets = updatePackageManager(targets, newUserId);
- updateActivityManager(affectedTargets, newUserId);
+ updateActivityManager(updatePackageManager(mImpl.updateOverlaysForUser(newUserId)));
}
persistSettings();
} finally {
@@ -417,19 +411,11 @@
traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
for (final int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
- false);
- if (pi != null && !pi.applicationInfo.isInstantApp()) {
- mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+ final AndroidPackage pkg = mPackageManager.onPackageAdded(
+ packageName, userId);
+ if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- if (pi.isOverlayPackage()) {
- mImpl.onOverlayPackageAdded(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- } else {
- mImpl.onTargetPackageAdded(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- }
+ updateTargetPackages(mImpl.onPackageAdded(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageAdded internal error", e);
}
@@ -447,19 +433,11 @@
traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
- false);
- if (pi != null && pi.applicationInfo.isInstantApp()) {
- mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+ final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+ packageName, userId);
+ if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- if (pi.isOverlayPackage()) {
- mImpl.onOverlayPackageChanged(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- } else {
- mImpl.onTargetPackageChanged(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- }
+ updateTargetPackages(mImpl.onPackageChanged(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageChanged internal error", e);
}
@@ -477,12 +455,11 @@
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
- mPackageManager.forgetPackageInfo(packageName, userId);
- final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
- if (oi != null) {
+ final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+ packageName, userId);
+ if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- mImpl.onOverlayPackageReplacing(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
+ updateTargetPackages(mImpl.onPackageReplacing(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplacing internal error", e);
}
@@ -500,18 +477,11 @@
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
- false);
- if (pi != null && !pi.applicationInfo.isInstantApp()) {
- mPackageManager.cachePackageInfo(packageName, userId, pi);
+ final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+ packageName, userId);
+ if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- if (pi.isOverlayPackage()) {
- mImpl.onOverlayPackageReplaced(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- } else {
- mImpl.onTargetPackageReplaced(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- }
+ updateTargetPackages(mImpl.onPackageReplaced(packageName, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplaced internal error", e);
}
@@ -529,20 +499,8 @@
traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
for (int userId : userIds) {
synchronized (mLock) {
- mPackageManager.forgetPackageInfo(packageName, userId);
- final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-
- try {
- if (oi != null) {
- mImpl.onOverlayPackageRemoved(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- } else {
- mImpl.onTargetPackageRemoved(packageName, userId)
- .ifPresent(mPropagateOverlayChange);
- }
- } catch (OperationFailedException e) {
- Slog.e(TAG, "onPackageRemoved internal error", e);
- }
+ mPackageManager.onPackageRemoved(packageName, userId);
+ updateTargetPackages(mImpl.onPackageRemoved(packageName, userId));
}
}
} finally {
@@ -560,11 +518,9 @@
if (userId != UserHandle.USER_NULL) {
try {
traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
- final ArrayList<String> targets;
synchronized (mLock) {
- targets = mImpl.updateOverlaysForUser(userId);
+ updatePackageManager(mImpl.updateOverlaysForUser(userId));
}
- updatePackageManager(targets, userId);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -628,16 +584,22 @@
@Override
public OverlayInfo getOverlayInfo(@Nullable final String packageName,
final int userIdArg) {
- if (packageName == null) {
+ return getOverlayInfoByIdentifier(new OverlayIdentifier(packageName), userIdArg);
+ }
+
+ @Override
+ public OverlayInfo getOverlayInfoByIdentifier(@Nullable final OverlayIdentifier overlay,
+ final int userIdArg) {
+ if (overlay == null || overlay.getPackageName() == null) {
return null;
}
try {
- traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + packageName);
+ traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + overlay);
final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfo");
synchronized (mLock) {
- return mImpl.getOverlayInfo(packageName, realUserId);
+ return mImpl.getOverlayInfo(overlay, realUserId);
}
} finally {
traceEnd(TRACE_TAG_RRO);
@@ -653,15 +615,16 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabled");
- enforceActor(packageName, "setEnabled", realUserId);
+ enforceActor(overlay, "setEnabled", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setEnabled(packageName, enable, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ updateTargetPackages(mImpl.setEnabled(overlay, enable, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
@@ -684,16 +647,18 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setEnabledExclusive");
- enforceActor(packageName, "setEnabledExclusive", realUserId);
+ enforceActor(overlay, "setEnabledExclusive", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setEnabledExclusive(packageName,
+ mImpl.setEnabledExclusive(overlay,
false /* withinCategory */, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ .ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -716,17 +681,19 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg,
"setEnabledExclusiveInCategory");
- enforceActor(packageName, "setEnabledExclusiveInCategory", realUserId);
+ enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setEnabledExclusive(packageName,
+ mImpl.setEnabledExclusive(overlay,
true /* withinCategory */, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ .ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -750,15 +717,18 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
+ parentPackageName);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
+ final OverlayIdentifier parentOverlay = new OverlayIdentifier(parentPackageName);
final int realUserId = handleIncomingUser(userIdArg, "setPriority");
- enforceActor(packageName, "setPriority", realUserId);
+ enforceActor(overlay, "setPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setPriority(packageName, parentPackageName, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ mImpl.setPriority(overlay, parentOverlay, realUserId)
+ .ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -780,15 +750,16 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setHighestPriority");
- enforceActor(packageName, "setHighestPriority", realUserId);
+ enforceActor(overlay, "setHighestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setHighestPriority(packageName, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ updateTargetPackages(mImpl.setHighestPriority(overlay, realUserId));
return true;
} catch (OperationFailedException e) {
return false;
@@ -810,15 +781,17 @@
try {
traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
+
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "setLowestPriority");
- enforceActor(packageName, "setLowestPriority", realUserId);
+ enforceActor(overlay, "setLowestPriority", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
- mImpl.setLowestPriority(packageName, realUserId)
- .ifPresent(mPropagateOverlayChange);
+ mImpl.setLowestPriority(overlay, realUserId)
+ .ifPresent(OverlayManagerService.this::updateTargetPackages);
return true;
} catch (OperationFailedException e) {
return false;
@@ -858,12 +831,17 @@
return;
}
+ final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
final int realUserId = handleIncomingUser(userIdArg, "invalidateCachesForOverlay");
- enforceActor(packageName, "invalidateCachesForOverlay", realUserId);
+ enforceActor(overlay, "invalidateCachesForOverlay", realUserId);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- mImpl.removeIdmapForOverlay(packageName, realUserId);
+ try {
+ mImpl.removeIdmapForOverlay(overlay, realUserId);
+ } catch (OperationFailedException e) {
+ Slog.w(TAG, "invalidate caches for overlay '" + overlay + "' failed", e);
+ }
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -893,25 +871,63 @@
}
}
- private Optional<PackageAndUser> executeRequest(
- @NonNull final OverlayManagerTransaction.Request request) throws Exception {
- final int realUserId = handleIncomingUser(request.userId, request.typeToString());
- enforceActor(request.packageName, request.typeToString(), realUserId);
+ private Set<PackageAndUser> executeRequest(
+ @NonNull final OverlayManagerTransaction.Request request)
+ throws OperationFailedException {
+ Objects.requireNonNull(request, "Transaction contains a null request");
+ Objects.requireNonNull(request.overlay,
+ "Transaction overlay identifier must be non-null");
+
+ final int callingUid = Binder.getCallingUid();
+ final int realUserId;
+ if (request.type == TYPE_REGISTER_FABRICATED
+ || request.type == TYPE_UNREGISTER_FABRICATED) {
+ if (request.userId != UserHandle.USER_ALL) {
+ throw new IllegalArgumentException(request.typeToString()
+ + " unsupported for user " + request.userId);
+ }
+ realUserId = UserHandle.USER_ALL;
+
+ // Enforce that the calling process can only register and unregister fabricated
+ // overlays using its package name.
+ final String pkgName = request.overlay.getPackageName();
+ if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
+ mPackageManager.getPackagesForUid(callingUid), pkgName)) {
+ throw new IllegalArgumentException("UID " + callingUid + " does own package"
+ + "name " + pkgName);
+ }
+ } else {
+ // Enforce actor requirements for enabling, disabling, and reordering overlays.
+ realUserId = handleIncomingUser(request.userId, request.typeToString());
+ enforceActor(request.overlay, request.typeToString(), realUserId);
+ }
final long ident = Binder.clearCallingIdentity();
try {
switch (request.type) {
case TYPE_SET_ENABLED:
- Optional<PackageAndUser> opt1 =
- mImpl.setEnabled(request.packageName, true, request.userId);
- Optional<PackageAndUser> opt2 =
- mImpl.setHighestPriority(request.packageName, request.userId);
- // Both setEnabled and setHighestPriority affected the same
- // target package and user: if both return non-empty
- // Optionals, they are identical
- return opt1.isPresent() ? opt1 : opt2;
+ Set<PackageAndUser> result = null;
+ result = CollectionUtils.addAll(result,
+ mImpl.setEnabled(request.overlay, true, realUserId));
+ result = CollectionUtils.addAll(result,
+ mImpl.setHighestPriority(request.overlay, realUserId));
+ return CollectionUtils.emptyIfNull(result);
+
case TYPE_SET_DISABLED:
- return mImpl.setEnabled(request.packageName, false, request.userId);
+ return mImpl.setEnabled(request.overlay, false, realUserId);
+
+ case TYPE_REGISTER_FABRICATED:
+ final FabricatedOverlayInternal fabricated =
+ request.extras.getParcelable(
+ OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
+ );
+ Objects.requireNonNull(fabricated,
+ "no fabricated overlay attached to request");
+ return mImpl.registerFabricatedOverlay(fabricated);
+
+ case TYPE_UNREGISTER_FABRICATED:
+ return mImpl.unregisterFabricatedOverlay(request.overlay);
+
default:
throw new IllegalArgumentException("unsupported request: " + request);
}
@@ -921,7 +937,7 @@
}
private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
- throws Exception {
+ throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "commit " + transaction);
}
@@ -931,25 +947,25 @@
// map: userId -> set<package-name>: target packages of overlays in
// this transaction
- SparseArray<Set<String>> transactionTargets = new SparseArray<>();
+ final SparseArray<Set<String>> transactionTargets = new SparseArray<>();
// map: userId -> set<package-name>: packages that need to reload
// their resources due to changes to the overlays in this
// transaction
- SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
+ final SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
synchronized (mLock) {
-
// execute the requests (as calling user)
for (final OverlayManagerTransaction.Request request : transaction) {
- executeRequest(request).ifPresent(target -> {
- Set<String> userTargets = transactionTargets.get(target.userId);
- if (userTargets == null) {
- userTargets = new ArraySet<String>();
- transactionTargets.put(target.userId, userTargets);
- }
- userTargets.add(target.packageName);
- });
+ executeRequest(request).forEach(
+ target -> {
+ Set<String> userTargets = transactionTargets.get(target.userId);
+ if (userTargets == null) {
+ userTargets = new ArraySet<>();
+ transactionTargets.put(target.userId, userTargets);
+ }
+ userTargets.add(target.packageName);
+ });
}
// past the point of no return: the entire transaction has been
@@ -975,10 +991,7 @@
final long ident = Binder.clearCallingIdentity();
try {
// schedule apps to refresh
- for (int index = 0; index < affectedPackagesToUpdate.size(); index++) {
- final int userId = affectedPackagesToUpdate.keyAt(index);
- updateActivityManager(affectedPackagesToUpdate.valueAt(index), userId);
- }
+ updateActivityManager(affectedPackagesToUpdate);
// broadcast the ACTION_OVERLAY_CHANGED intents
for (int index = 0; index < transactionTargets.size(); index++) {
@@ -1059,12 +1072,12 @@
dumpState.setField(arg);
break;
default:
- dumpState.setPackageName(arg);
+ dumpState.setOverlyIdentifier(arg);
break;
}
}
if (dumpState.getPackageName() == null && opti < args.length) {
- dumpState.setPackageName(args[opti]);
+ dumpState.setOverlyIdentifier(args[opti]);
opti++;
}
@@ -1101,12 +1114,12 @@
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
}
- private void enforceActor(String packageName, String methodName, int realUserId)
- throws SecurityException {
- OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, realUserId);
+ private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
+ int realUserId) throws SecurityException {
+ OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
if (overlayInfo == null) {
throw new IllegalArgumentException("Unable to retrieve overlay information for "
- + packageName);
+ + overlay);
}
int callingUid = Binder.getCallingUid();
@@ -1115,7 +1128,13 @@
};
private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-
+ private static class AndroidPackageUsers {
+ private AndroidPackage mPackage;
+ private final Set<Integer> mInstalledUsers = new ArraySet<>();
+ private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
+ this.mPackage = pkg;
+ }
+ }
private final Context mContext;
private final IPackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -1125,7 +1144,8 @@
// intent, querying the PackageManagerService for the actual current
// state may lead to contradictions within OMS. Better then to lag
// behind until all pending intents have been processed.
- private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
+ private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
+ private final Set<Integer> mInitializedUsers = new ArraySet<>();
PackageManagerHelperImpl(Context context) {
mContext = context;
@@ -1133,29 +1153,112 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
- public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
- final boolean useCache) {
- if (useCache) {
- final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
- if (cachedPi != null) {
- return cachedPi;
+ /**
+ * Initializes the helper for the user. This only needs to be invoked one time before
+ * packages of this user are queried.
+ * @param userId the user id to initialize
+ * @return a map of package name to all packages installed in the user
+ */
+ @NonNull
+ public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
+ if (!mInitializedUsers.contains(userId)) {
+ mInitializedUsers.add(userId);
+ mPackageManagerInternal.forEachInstalledPackage(
+ (pkg) -> addPackageUser(pkg, userId), userId);
+ }
+
+ final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
+ for (int i = 0, n = mCache.size(); i < n; i++) {
+ final AndroidPackageUsers pkg = mCache.valueAt(i);
+ if (pkg.mInstalledUsers.contains(userId)) {
+ userPackages.put(mCache.keyAt(i), pkg.mPackage);
}
}
- try {
- final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
- if (useCache && pi != null) {
- cachePackageInfo(packageName, userId, pi);
- }
- return pi;
- } catch (RemoteException e) {
- // Intentionally left empty.
- }
- return null;
+ return userPackages;
}
@Override
- public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
- return getPackageInfo(packageName, userId, true);
+ @Nullable
+ public AndroidPackage getPackageForUser(@NonNull final String packageName,
+ final int userId) {
+ final AndroidPackageUsers pkg = mCache.get(packageName);
+ if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
+ return pkg.mPackage;
+ }
+ try {
+ if (!mPackageManager.isPackageAvailable(packageName, userId)) {
+ return null;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to check availability of package '" + packageName
+ + "' for user " + userId, e);
+ return null;
+ }
+ return addPackageUser(packageName, userId);
+ }
+
+ @NonNull
+ private AndroidPackage addPackageUser(@NonNull final String packageName,
+ final int user) {
+ final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
+ if (pkg == null) {
+ Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
+ + " continuing as if package was never added", new Throwable());
+ return null;
+ }
+ return addPackageUser(pkg, user);
+ }
+
+ @NonNull
+ private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
+ final int user) {
+ AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
+ if (pkgUsers == null) {
+ pkgUsers = new AndroidPackageUsers(pkg);
+ mCache.put(pkg.getPackageName(), pkgUsers);
+ } else {
+ pkgUsers.mPackage = pkg;
+ }
+ pkgUsers.mInstalledUsers.add(user);
+ return pkgUsers.mPackage;
+ }
+
+
+ @NonNull
+ private void removePackageUser(@NonNull final String packageName, final int user) {
+ final AndroidPackageUsers pkgUsers = mCache.get(packageName);
+ if (pkgUsers == null) {
+ return;
+ }
+ removePackageUser(pkgUsers, user);
+ }
+
+ @NonNull
+ private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
+ pkg.mInstalledUsers.remove(user);
+ if (pkg.mInstalledUsers.isEmpty()) {
+ mCache.remove(pkg.mPackage.getPackageName());
+ }
+ }
+
+ @Nullable
+ public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
+ return addPackageUser(packageName, userId);
+ }
+
+ @Nullable
+ public AndroidPackage onPackageUpdated(@NonNull final String packageName,
+ final int userId) {
+ return addPackageUser(packageName, userId);
+ }
+
+ public void onPackageRemoved(@NonNull final String packageName, final int userId) {
+ removePackageUser(packageName, userId);
+ }
+
+ @Override
+ public boolean isInstantApp(@NonNull final String packageName, final int userId) {
+ return mPackageManagerInternal.isInstantApp(packageName, userId);
}
@NonNull
@@ -1179,15 +1282,6 @@
}
@Override
- public List<PackageInfo> getOverlayPackages(final int userId) {
- final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
- for (final PackageInfo info : overlays) {
- cachePackageInfo(info.packageName, userId, info);
- }
- return overlays;
- }
-
- @Override
public String getConfigSignaturePackage() {
final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE,
@@ -1200,16 +1294,14 @@
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId)
throws IOException {
- PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
ApkAssets apkAssets = null;
try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.getOverlayableInfo(targetOverlayableName);
} finally {
if (apkAssets != null) {
@@ -1224,16 +1316,14 @@
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
throws IOException {
- PackageInfo packageInfo = getPackageInfo(targetPackageName, userId);
+ AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
if (packageInfo == null) {
throw new IOException("Unable to get target package");
}
- String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
ApkAssets apkAssets = null;
try {
- apkAssets = ApkAssets.loadFromPath(baseCodePath);
+ apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
return apkAssets.definesOverlayable();
} finally {
if (apkAssets != null) {
@@ -1250,35 +1340,10 @@
mContext.enforceCallingOrSelfPermission(permission, message);
}
- public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
- final int userId) {
- final HashMap<String, PackageInfo> map = mCache.get(userId);
- return map == null ? null : map.get(packageName);
- }
-
- public void cachePackageInfo(@NonNull final String packageName, final int userId,
- @NonNull final PackageInfo pi) {
- HashMap<String, PackageInfo> map = mCache.get(userId);
- if (map == null) {
- map = new HashMap<>();
- mCache.put(userId, map);
- }
- map.put(packageName, pi);
- }
-
- public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
- final HashMap<String, PackageInfo> map = mCache.get(userId);
- if (map == null) {
- return;
- }
- map.remove(packageName);
- if (map.isEmpty()) {
- mCache.delete(userId);
- }
- }
-
public void forgetAllPackageInfos(final int userId) {
- mCache.delete(userId);
+ for (int i = 0, n = mCache.size(); i < n; i++) {
+ removePackageUser(mCache.valueAt(i), userId);
+ }
}
@Nullable
@@ -1292,19 +1357,12 @@
}
private static final String TAB1 = " ";
- private static final String TAB2 = TAB1 + TAB1;
public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
- pw.println("PackageInfo cache");
+ pw.println("AndroidPackage cache");
if (!dumpState.isVerbose()) {
- int count = 0;
- final int n = mCache.size();
- for (int i = 0; i < n; i++) {
- final int userId = mCache.keyAt(i);
- count += mCache.get(userId).size();
- }
- pw.println(TAB1 + count + " package(s)");
+ pw.println(TAB1 + mCache.size() + " package(s)");
return;
}
@@ -1313,25 +1371,70 @@
return;
}
- final int n = mCache.size();
- for (int i = 0; i < n; i++) {
- final int userId = mCache.keyAt(i);
- pw.println(TAB1 + "User " + userId);
- final HashMap<String, PackageInfo> map = mCache.get(userId);
- for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
- pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
- }
+ for (int i = 0, n = mCache.size(); i < n; i++) {
+ final String packageName = mCache.keyAt(i);
+ final AndroidPackageUsers pkg = mCache.valueAt(i);
+ pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
+ pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
}
}
}
+ private void updateTargetPackages(@Nullable PackageAndUser updatedTarget) {
+ if (updatedTarget != null) {
+ updateTargetPackages(Set.of(updatedTarget));
+ }
+ }
+
+ private void updateTargetPackages(@Nullable Set<PackageAndUser> updatedTargets) {
+ if (CollectionUtils.isEmpty(updatedTargets)) {
+ return;
+ }
+ persistSettings();
+ final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(updatedTargets);
+ FgThread.getHandler().post(() -> {
+ for (int i = 0, n = userTargets.size(); i < n; i++) {
+ final ArraySet<String> targets = userTargets.valueAt(i);
+ final int userId = userTargets.keyAt(i);
+
+ // Update the overlay paths in package manager.
+ final List<String> affectedPackages = updatePackageManager(targets, userId);
+ updateActivityManager(affectedPackages, userId);
+
+ // Overlays targeting shared libraries may cause more packages to need to be
+ // refreshed.
+ broadcastActionOverlayChanged(targets, userId);
+ }
+ });
+ }
+
+ @Nullable
+ private static SparseArray<ArraySet<String>> groupTargetsByUserId(
+ @Nullable final Set<PackageAndUser> targetsAndUsers) {
+ final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
+ CollectionUtils.forEach(targetsAndUsers, target -> {
+ ArraySet<String> targets = userTargets.get(target.userId);
+ if (targets == null) {
+ targets = new ArraySet<>();
+ userTargets.put(target.userId, targets);
+ }
+ targets.add(target.packageName);
+ });
+ return userTargets;
+ }
+
// Helper methods to update other parts of the system or read/write
// settings: these methods should never call into each other!
- private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
+ private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
+ CollectionUtils.forEach(targetPackages,
+ target -> broadcastActionOverlayChanged(target, userId));
+ }
+
+ private static void broadcastActionOverlayChanged(String targetPackage, final int userId) {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
- Uri.fromParts("package", targetPackageName, null));
+ Uri.fromParts("package", targetPackage, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
@@ -1345,7 +1448,7 @@
* Tell the activity manager to tell a set of packages to reload their
* resources.
*/
- private void updateActivityManager(List<String> targetPackageNames, final int userId) {
+ private void updateActivityManager(@NonNull List<String> targetPackageNames, final int userId) {
final IActivityManager am = ActivityManager.getService();
try {
am.scheduleApplicationInfoChanged(targetPackageNames, userId);
@@ -1354,16 +1457,33 @@
}
}
- private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) {
- return updatePackageManager(Collections.singletonList(targetPackageNames), userId);
+ private void updateActivityManager(@NonNull SparseArray<List<String>> targetPackageNames) {
+ for (int i = 0, n = targetPackageNames.size(); i < n; i++) {
+ updateActivityManager(targetPackageNames.valueAt(i), targetPackageNames.keyAt(i));
+ }
+ }
+
+ @NonNull
+ private SparseArray<List<String>> updatePackageManager(@Nullable Set<PackageAndUser> targets) {
+ if (CollectionUtils.isEmpty(targets)) {
+ return new SparseArray<>();
+ }
+ final SparseArray<List<String>> affectedTargets = new SparseArray<>();
+ final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(targets);
+ for (int i = 0, n = userTargets.size(); i < n; i++) {
+ final int userId = userTargets.keyAt(i);
+ affectedTargets.put(userId, updatePackageManager(userTargets.valueAt(i), userId));
+ }
+ return affectedTargets;
}
/**
* Updates the target packages' set of enabled overlays in PackageManager.
* @return the package names of affected targets (a superset of
- * targetPackageNames: the target themserlves and shared libraries)
+ * targetPackageNames: the target themselves and shared libraries)
*/
- private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
+ @NonNull
+ private List<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
final int userId) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c547c36..fb183f5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -28,26 +28,31 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.om.CriticalOverlayInfo;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.content.om.OverlayConfig;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Internal implementation of OverlayManagerService.
@@ -85,29 +90,42 @@
* should either scrap the overlay manager's previous settings or merge the old
* settings with the new.
*/
- private boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
+ private boolean mustReinitializeOverlay(@NonNull final AndroidPackage theTruth,
@Nullable final OverlayInfo oldSettings) {
if (oldSettings == null) {
return true;
}
- if (!Objects.equals(theTruth.overlayTarget, oldSettings.targetPackageName)) {
+ if (!Objects.equals(theTruth.getOverlayTarget(), oldSettings.targetPackageName)) {
return true;
}
- if (!Objects.equals(theTruth.targetOverlayableName, oldSettings.targetOverlayableName)) {
+ if (!Objects.equals(theTruth.getOverlayTargetName(), oldSettings.targetOverlayableName)) {
return true;
}
-
- boolean isMutable = isPackageConfiguredMutable(theTruth.packageName);
+ if (oldSettings.isFabricated) {
+ return true;
+ }
+ boolean isMutable = isPackageConfiguredMutable(theTruth);
if (isMutable != oldSettings.isMutable) {
return true;
}
-
// If an immutable overlay changes its configured enabled state, reinitialize the overlay.
- if (!isMutable && isPackageConfiguredEnabled(theTruth.packageName)
- != oldSettings.isEnabled()) {
+ if (!isMutable && isPackageConfiguredEnabled(theTruth) != oldSettings.isEnabled()) {
return true;
}
+ return false;
+ }
+ private boolean mustReinitializeOverlay(@NonNull final FabricatedOverlayInfo theTruth,
+ @Nullable final OverlayInfo oldSettings) {
+ if (oldSettings == null) {
+ return true;
+ }
+ if (!Objects.equals(theTruth.targetPackageName, oldSettings.targetPackageName)) {
+ return true;
+ }
+ if (!Objects.equals(theTruth.targetOverlayable, oldSettings.targetOverlayableName)) {
+ return true;
+ }
return false;
}
@@ -129,87 +147,45 @@
* of two sets: the set of targets with currently active overlays, and the
* set of targets that had, but no longer have, active overlays.
*/
- ArrayList<String> updateOverlaysForUser(final int newUserId) {
+ @NonNull
+ ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
if (DEBUG) {
Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
}
- final Set<String> packagesToUpdateAssets = new ArraySet<>();
- final ArrayMap<String, List<OverlayInfo>> tmp = mSettings.getOverlaysForUser(newUserId);
- final int tmpSize = tmp.size();
- final ArrayMap<String, OverlayInfo> storedOverlayInfos = new ArrayMap<>(tmpSize);
- for (int i = 0; i < tmpSize; i++) {
- final List<OverlayInfo> chunk = tmp.valueAt(i);
- final int chunkSize = chunk.size();
- for (int j = 0; j < chunkSize; j++) {
- final OverlayInfo oi = chunk.get(j);
- storedOverlayInfos.put(oi.packageName, oi);
- }
- }
+ // Remove the settings of all overlays that are no longer installed for this user.
+ final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+ final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
+ newUserId);
+ CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
+ (info) -> !userPackages.containsKey(info.packageName), newUserId));
- // Reset overlays if something critical like the target package name
- // has changed
- List<PackageInfo> overlayPackages = mPackageManager.getOverlayPackages(newUserId);
- final int overlayPackagesSize = overlayPackages.size();
- for (int i = 0; i < overlayPackagesSize; i++) {
- final PackageInfo overlayPackage = overlayPackages.get(i);
- final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
-
- int priority = getPackageConfiguredPriority(overlayPackage.packageName);
- if (mustReinitializeOverlay(overlayPackage, oi)) {
- // if targetPackageName has changed the package that *used* to
- // be the target must also update its assets
- if (oi != null) {
- packagesToUpdateAssets.add(oi.targetPackageName);
- }
-
- mSettings.init(overlayPackage.packageName, newUserId,
- overlayPackage.overlayTarget,
- overlayPackage.targetOverlayableName,
- overlayPackage.applicationInfo.getBaseCodePath(),
- isPackageConfiguredMutable(overlayPackage.packageName),
- isPackageConfiguredEnabled(overlayPackage.packageName),
- priority, overlayPackage.overlayCategory);
- } else if (priority != oi.priority) {
- mSettings.setPriority(overlayPackage.packageName, newUserId, priority);
- packagesToUpdateAssets.add(oi.targetPackageName);
- }
-
- storedOverlayInfos.remove(overlayPackage.packageName);
- }
-
- // any OverlayInfo left in storedOverlayInfos is no longer
- // installed and should be removed
- final int storedOverlayInfosSize = storedOverlayInfos.size();
- for (int i = 0; i < storedOverlayInfosSize; i++) {
- final OverlayInfo oi = storedOverlayInfos.valueAt(i);
- mSettings.remove(oi.packageName, oi.userId);
- removeIdmapIfPossible(oi);
- packagesToUpdateAssets.add(oi.targetPackageName);
- }
-
- // make sure every overlay's state is up-to-date; this needs to happen
- // after old overlays have been removed, or we risk removing a
- // legitimate idmap file if a new overlay package has the same apk path
- // as the removed overlay package used to have
- for (int i = 0; i < overlayPackagesSize; i++) {
- final PackageInfo overlayPackage = overlayPackages.get(i);
+ // Update the state of all installed packages containing overlays, and initialize new
+ // overlays that are not currently in the settings.
+ for (int i = 0, n = userPackages.size(); i < n; i++) {
+ final AndroidPackage pkg = userPackages.valueAt(i);
try {
- updateState(overlayPackage.overlayTarget, overlayPackage.packageName,
- newUserId, 0);
- } catch (OverlayManagerSettings.BadKeyException e) {
- Slog.e(TAG, "failed to update settings", e);
- mSettings.remove(overlayPackage.packageName, newUserId);
+ CollectionUtils.addAll(updatedTargets,
+ updatePackageOverlays(pkg, newUserId, 0 /* flags */));
+
+ // When a new user is switched to for the first time, package manager must be
+ // informed of the overlay paths for all packages installed in the user.
+ updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
+ + "' for user " + newUserId + "", e);
}
- packagesToUpdateAssets.add(overlayPackage.overlayTarget);
}
- // remove target packages that are not installed
- final Iterator<String> iter = packagesToUpdateAssets.iterator();
- while (iter.hasNext()) {
- String targetPackageName = iter.next();
- if (mPackageManager.getPackageInfo(targetPackageName, newUserId) == null) {
- iter.remove();
+ // Update the state of all fabricated overlays, and initialize fabricated overlays in the
+ // new user.
+ for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) {
+ try {
+ CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay(
+ info, newUserId));
+ } catch (OperationFailedException e) {
+ Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path
+ + "' for user " + newUserId + "", e);
}
}
@@ -232,14 +208,20 @@
// Enable the default overlay if its category does not have a single overlay enabled.
for (final String defaultOverlay : mDefaultOverlays) {
try {
- final OverlayInfo oi = mSettings.getOverlayInfo(defaultOverlay, newUserId);
+ // OverlayConfig is the new preferred way to enable overlays by default. This legacy
+ // default enabled method was created before overlays could have a name specified.
+ // Only allow enabling overlays without a name using this mechanism.
+ final OverlayIdentifier overlay = new OverlayIdentifier(defaultOverlay);
+
+ final OverlayInfo oi = mSettings.getOverlayInfo(overlay, newUserId);
if (!enabledCategories.contains(oi.category)) {
Slog.w(TAG, "Enabling default overlay '" + defaultOverlay + "' for target '"
+ oi.targetPackageName + "' in category '" + oi.category + "' for user "
+ newUserId);
- mSettings.setEnabled(oi.packageName, newUserId, true);
- if (updateState(oi.targetPackageName, oi.packageName, newUserId, 0)) {
- packagesToUpdateAssets.add(oi.targetPackageName);
+ mSettings.setEnabled(overlay, newUserId, true);
+ if (updateState(oi, newUserId, 0)) {
+ CollectionUtils.add(updatedTargets,
+ new PackageAndUser(oi.targetPackageName, oi.userId));
}
}
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -248,7 +230,8 @@
}
}
- return new ArrayList<>(packagesToUpdateAssets);
+ cleanStaleResourceCache();
+ return updatedTargets;
}
void onUserRemoved(final int userId) {
@@ -258,236 +241,151 @@
mSettings.removeUser(userId);
}
- Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName,
+ @NonNull
+ Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
- }
-
- return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+ return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
}
- Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName,
+ @NonNull
+ Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
- }
-
- return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+ return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
}
- Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId="
- + userId);
- }
-
- return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
- }
-
- Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId);
- }
-
- return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
- }
-
- Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
- }
-
- return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
- }
-
- /**
- * Update the state of any overlays for this target.
- */
- private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget(
- @NonNull final String targetPackageName, final int userId, final int flags)
+ @NonNull
+ Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
throws OperationFailedException {
- final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName,
- userId);
+ return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+ }
- // Update the state for any overlay that targets this package.
+ @NonNull
+ Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+ throws OperationFailedException {
+ return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+ }
+
+ @NonNull
+ Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
+ }
+ // Update the state of all overlays that target this package.
+ final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+
+ // Remove all the overlays this package declares.
+ return CollectionUtils.addAll(targets,
+ removeOverlaysForUser(oi -> pkgName.equals(oi.packageName), userId));
+ }
+
+ @NonNull
+ private Set<PackageAndUser> removeOverlaysForUser(
+ @NonNull final Predicate<OverlayInfo> condition, final int userId) {
+ final List<OverlayInfo> overlays = mSettings.removeIf(
+ io -> userId == io.userId && condition.test(io) );
+ Set<PackageAndUser> targets = Collections.emptySet();
+ for (int i = 0, n = overlays.size(); i < n; i++) {
+ final OverlayInfo info = overlays.get(i);
+ targets = CollectionUtils.add(targets,
+ new PackageAndUser(info.targetPackageName, userId));
+
+ // Remove the idmap if the overlay is no longer installed for any user.
+ removeIdmapIfPossible(info);
+ }
+ return targets;
+ }
+
+ @NonNull
+ private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+ final int userId, final int flags) {
boolean modified = false;
- for (final OverlayInfo oi : targetOverlays) {
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
- userId);
- if (overlayPackage == null) {
- modified |= mSettings.remove(oi.packageName, oi.userId);
- removeIdmapIfPossible(oi);
- } else {
- try {
- modified |= updateState(targetPackageName, oi.packageName, userId, flags);
- } catch (OverlayManagerSettings.BadKeyException e) {
- Slog.e(TAG, "failed to update settings", e);
- modified |= mSettings.remove(oi.packageName, userId);
- }
+ final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
+ for (int i = 0, n = overlays.size(); i < n; i++) {
+ final OverlayInfo oi = overlays.get(i);
+ try {
+ modified |= updateState(oi, userId, flags);
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to update settings", e);
+ modified |= mSettings.remove(oi.getOverlayIdentifier(), userId);
}
}
-
if (!modified) {
- // Update the overlay paths of the target within package manager if necessary.
- final List<String> enabledOverlayPaths = new ArrayList<>(targetOverlays.size());
-
- // Framework overlays are first in the overlay paths of a package within PackageManager.
- for (final OverlayInfo oi : mSettings.getOverlaysForTarget("android", userId)) {
- if (oi.isEnabled()) {
- enabledOverlayPaths.add(oi.baseCodePath);
- }
- }
-
- for (final OverlayInfo oi : targetOverlays) {
- if (oi.isEnabled()) {
- enabledOverlayPaths.add(oi.baseCodePath);
- }
- }
-
- // TODO(): Use getEnabledOverlayPaths(userId, targetPackageName) instead of
- // resourceDirs if in the future resourceDirs contains APKs other than overlays
- PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
- ApplicationInfo appInfo = packageInfo == null ? null : packageInfo.applicationInfo;
- String[] resourceDirs = appInfo == null ? null : appInfo.resourceDirs;
-
- // If the lists aren't the same length, the enabled overlays have changed
- if (ArrayUtils.size(resourceDirs) != enabledOverlayPaths.size()) {
- modified = true;
- } else if (resourceDirs != null) {
- // If any element isn't equal, an overlay or the order of overlays has changed
- for (int index = 0; index < resourceDirs.length; index++) {
- if (!resourceDirs[index].equals(enabledOverlayPaths.get(index))) {
- modified = true;
- break;
- }
- }
- }
+ return Collections.emptySet();
}
-
- if (modified) {
- return Optional.of(new PackageAndUser(targetPackageName, userId));
- }
- return Optional.empty();
+ return Set.of(new PackageAndUser(targetPackage, userId));
}
- Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
+ @NonNull
+ private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+ final int userId, final int flags) throws OperationFailedException {
+ if (pkg.getOverlayTarget() == null) {
+ // This package does not have overlays declared in its manifest.
+ return Collections.emptySet();
}
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
- return onOverlayPackageRemoved(packageName, userId);
- }
-
- mSettings.init(packageName, userId, overlayPackage.overlayTarget,
- overlayPackage.targetOverlayableName,
- overlayPackage.applicationInfo.getBaseCodePath(),
- isPackageConfiguredMutable(overlayPackage.packageName),
- isPackageConfiguredEnabled(overlayPackage.packageName),
- getPackageConfiguredPriority(overlayPackage.packageName),
- overlayPackage.overlayCategory);
+ Set<PackageAndUser> updatedTargets = Collections.emptySet();
+ final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
+ final int priority = getPackageConfiguredPriority(pkg);
try {
- if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
- return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
- }
- return Optional.empty();
- } catch (OverlayManagerSettings.BadKeyException e) {
- mSettings.remove(packageName, userId);
- throw new OperationFailedException("failed to update settings", e);
- }
- }
+ OverlayInfo currentInfo = mSettings.getNullableOverlayInfo(overlay, userId);
+ if (mustReinitializeOverlay(pkg, currentInfo)) {
+ if (currentInfo != null) {
+ // If the targetPackageName has changed, the package that *used* to
+ // be the target must also update its assets.
+ updatedTargets = CollectionUtils.add(updatedTargets,
+ new PackageAndUser(currentInfo.targetPackageName, userId));
+ }
- Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
- }
-
- try {
- final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- if (updateState(oi.targetPackageName, packageName, userId, 0)) {
- return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+ currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
+ pkg.getOverlayTargetName(), pkg.getBaseApkPath(),
+ isPackageConfiguredMutable(pkg),
+ isPackageConfiguredEnabled(pkg),
+ getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(),
+ false);
+ } else if (priority != currentInfo.priority) {
+ // Changing the priority of an overlay does not cause its settings to be
+ // reinitialized. Reorder the overlay and update its target package.
+ mSettings.setPriority(overlay, userId, priority);
+ updatedTargets = CollectionUtils.add(updatedTargets,
+ new PackageAndUser(currentInfo.targetPackageName, userId));
}
- return Optional.empty();
+
+ // Update the enabled state of the overlay.
+ if (updateState(currentInfo, userId, flags)) {
+ updatedTargets = CollectionUtils.add(updatedTargets,
+ new PackageAndUser(currentInfo.targetPackageName, userId));
+ }
} catch (OverlayManagerSettings.BadKeyException e) {
throw new OperationFailedException("failed to update settings", e);
}
+ return updatedTargets;
}
- Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
+ @NonNull
+ private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+ final int userId, final int flags) throws OperationFailedException {
if (DEBUG) {
- Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId="
- + userId);
+ Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
}
- try {
- final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- if (updateState(oi.targetPackageName, packageName, userId,
- FLAG_OVERLAY_IS_BEING_REPLACED)) {
- removeIdmapIfPossible(oi);
- return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
- }
- return Optional.empty();
- } catch (OverlayManagerSettings.BadKeyException e) {
- throw new OperationFailedException("failed to update settings", e);
- }
- }
+ // Update the state of overlays that target this package.
+ Set<PackageAndUser> updatedTargets = Collections.emptySet();
+ updatedTargets = CollectionUtils.addAll(updatedTargets,
+ updateOverlaysForTarget(pkgName, userId, flags));
- Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId="
- + userId);
- }
-
- final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
+ // Realign the overlay settings with PackageManager's view of the package.
+ final AndroidPackage pkg = mPackageManager.getPackageForUser(pkgName, userId);
if (pkg == null) {
- Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found");
- return onOverlayPackageRemoved(packageName, userId);
+ return onPackageRemoved(pkgName, userId);
}
- try {
- final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
- if (mustReinitializeOverlay(pkg, oldOi)) {
- mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
- pkg.applicationInfo.getBaseCodePath(),
- isPackageConfiguredMutable(pkg.packageName),
- isPackageConfiguredEnabled(pkg.packageName),
- getPackageConfiguredPriority(pkg.packageName), pkg.overlayCategory);
- }
-
- if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
- return Optional.of(new PackageAndUser(pkg.overlayTarget, userId));
- }
- return Optional.empty();
- } catch (OverlayManagerSettings.BadKeyException e) {
- throw new OperationFailedException("failed to update settings", e);
- }
+ // Update the state of the overlays this package declares in its manifest.
+ updatedTargets = CollectionUtils.addAll(updatedTargets,
+ updatePackageOverlays(pkg, userId, flags));
+ return updatedTargets;
}
- Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName,
- final int userId) throws OperationFailedException {
- try {
- final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
- if (mSettings.remove(packageName, userId)) {
- removeIdmapIfPossible(overlayInfo);
- return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
- }
- return Optional.empty();
- } catch (OverlayManagerSettings.BadKeyException e) {
- throw new OperationFailedException("failed to remove overlay", e);
- }
- }
-
- OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
+ OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier packageName, final int userId) {
try {
return mSettings.getOverlayInfo(packageName, userId);
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -504,94 +402,78 @@
return mSettings.getOverlaysForUser(userId);
}
- Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable,
- final int userId) throws OperationFailedException {
+ @NonNull
+ Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+ final boolean enable, final int userId) throws OperationFailedException {
if (DEBUG) {
- Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
- packageName, enable, userId));
- }
-
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- throw new OperationFailedException(
- String.format("failed to find overlay package %s for user %d",
- packageName, userId));
+ Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
+ overlay, enable, userId));
}
try {
- final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+ final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
if (!oi.isMutable) {
// Ignore immutable overlays.
throw new OperationFailedException(
"cannot enable immutable overlay packages in runtime");
}
- boolean modified = mSettings.setEnabled(packageName, userId, enable);
- modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
+ boolean modified = mSettings.setEnabled(overlay, userId, enable);
+ modified |= updateState(oi, userId, 0);
if (modified) {
- return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+ return Set.of(new PackageAndUser(oi.targetPackageName, userId));
}
- return Optional.empty();
+ return Set.of();
} catch (OverlayManagerSettings.BadKeyException e) {
throw new OperationFailedException("failed to update settings", e);
}
}
- Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName,
+ Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
boolean withinCategory, final int userId) throws OperationFailedException {
if (DEBUG) {
- Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
- + " withinCategory=%s userId=%d", packageName, withinCategory, userId));
- }
-
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- throw new OperationFailedException(String.format(
- "failed to find overlay package %s for user %d", packageName, userId));
+ Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
+ + " withinCategory=%s userId=%d", overlay, withinCategory, userId));
}
try {
- final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- final String targetPackageName = oi.targetPackageName;
+ final OverlayInfo enabledInfo = mSettings.getOverlayInfo(overlay, userId);
+ if (!enabledInfo.isMutable) {
+ throw new OperationFailedException(
+ "cannot enable immutable overlay packages in runtime");
+ }
- List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
+ // Remove the overlay to have enabled from the list of overlays to disable.
+ List<OverlayInfo> allOverlays = getOverlayInfosForTarget(enabledInfo.targetPackageName,
+ userId);
+ allOverlays.remove(enabledInfo);
boolean modified = false;
-
- // Disable all other overlays.
- allOverlays.remove(oi);
for (int i = 0; i < allOverlays.size(); i++) {
final OverlayInfo disabledInfo = allOverlays.get(i);
- final String disabledOverlayPackageName = disabledInfo.packageName;
- final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
- disabledOverlayPackageName, userId);
- if (disabledOverlayPackageInfo == null) {
- modified |= mSettings.remove(disabledOverlayPackageName, userId);
- continue;
- }
-
+ final OverlayIdentifier disabledOverlay = disabledInfo.getOverlayIdentifier();
if (!disabledInfo.isMutable) {
// Don't touch immutable overlays.
continue;
}
- if (withinCategory && !Objects.equals(disabledOverlayPackageInfo.overlayCategory,
- oi.category)) {
+ if (withinCategory && !Objects.equals(disabledInfo.category,
+ enabledInfo.category)) {
// Don't touch overlays from other categories.
continue;
}
// Disable the overlay.
- modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
- modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
+ modified |= mSettings.setEnabled(disabledOverlay, userId, false);
+ modified |= updateState(disabledInfo, userId, 0);
}
// Enable the selected overlay.
- modified |= mSettings.setEnabled(packageName, userId, true);
- modified |= updateState(targetPackageName, packageName, userId, 0);
+ modified |= mSettings.setEnabled(overlay, userId, true);
+ modified |= updateState(enabledInfo, userId, 0);
if (modified) {
- return Optional.of(new PackageAndUser(targetPackageName, userId));
+ return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
}
return Optional.empty();
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -599,87 +481,200 @@
}
}
- private boolean isPackageConfiguredMutable(@NonNull final String packageName) {
- return mOverlayConfig.isMutable(packageName);
- }
-
- private int getPackageConfiguredPriority(@NonNull final String packageName) {
- return mOverlayConfig.getPriority(packageName);
- }
-
- private boolean isPackageConfiguredEnabled(@NonNull final String packageName) {
- return mOverlayConfig.isEnabled(packageName);
- }
-
- Optional<PackageAndUser> setPriority(@NonNull final String packageName,
- @NonNull final String newParentPackageName, final int userId)
+ @NonNull
+ Set<PackageAndUser> registerFabricatedOverlay(
+ @NonNull final FabricatedOverlayInternal overlay)
throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
- + newParentPackageName + " userId=" + userId);
+ if (ParsingPackageUtils.validateName(overlay.overlayName,
+ false /* requireSeparator */, true /* requireFilename */) != null) {
+ throw new OperationFailedException(
+ "overlay name can only consist of alphanumeric characters, '_', and '.'");
}
- if (!isPackageConfiguredMutable(packageName)) {
- throw new OperationFailedException(String.format(
- "overlay package %s user %d is not updatable", packageName, userId));
+ final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay);
+ if (info == null) {
+ throw new OperationFailedException("failed to create fabricated overlay");
}
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- throw new OperationFailedException(String.format(
- "failed to find overlay package %s for user %d", packageName, userId));
+ final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ for (int userId : mSettings.getUsers()) {
+ updatedTargets.addAll(registerFabricatedOverlay(info, userId));
}
-
- if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
- return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
- }
- return Optional.empty();
+ return updatedTargets;
}
- Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName,
+ @NonNull
+ private Set<PackageAndUser> registerFabricatedOverlay(
+ @NonNull final FabricatedOverlayInfo info, int userId)
+ throws OperationFailedException {
+ final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
+ info.packageName, info.overlayName);
+ final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
+ if (oi != null) {
+ if (!oi.isFabricated) {
+ throw new OperationFailedException("non-fabricated overlay with name '" +
+ oi.overlayName + "' already present in '" + oi.packageName + "'");
+ }
+ }
+ try {
+ if (mustReinitializeOverlay(info, oi)) {
+ if (oi != null) {
+ // If the fabricated overlay changes its target package, update the previous
+ // target package so it no longer is overlaid.
+ updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+ }
+ oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
+ info.targetOverlayable, info.path, true, false,
+ OverlayConfig.DEFAULT_PRIORITY, null, true);
+ } else {
+ // The only non-critical part of the info that will change is path to the fabricated
+ // overlay.
+ mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
+ }
+ if (updateState(oi, userId, 0)) {
+ updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ throw new OperationFailedException("failed to update settings", e);
+ }
+
+ return updatedTargets;
+ }
+
+ @NonNull
+ Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+ final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ for (int userId : mSettings.getUsers()) {
+ updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
+ }
+ return updatedTargets;
+ }
+
+ @NonNull
+ private Set<PackageAndUser> unregisterFabricatedOverlay(
+ @NonNull final OverlayIdentifier overlay, int userId) {
+ final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
+ if (oi != null) {
+ mSettings.remove(overlay, userId);
+ if (oi.isEnabled()) {
+ // Removing a fabricated overlay only changes the overlay path of a package if it is
+ // currently enabled.
+ return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+ }
+ }
+ return Set.of();
+ }
+
+
+ private void cleanStaleResourceCache() {
+ // Clean up fabricated overlays that are no longer registered in any user.
+ final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+ for (final FabricatedOverlayInfo info : mIdmapManager.getFabricatedOverlayInfos()) {
+ if (!fabricatedPaths.contains(info.path)) {
+ mIdmapManager.deleteFabricatedOverlay(info.path);
+ }
+ }
+ }
+
+ /**
+ * Retrieves information about the fabricated overlays still in use.
+ * @return
+ */
+ @NonNull
+ private List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+ final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+ // Filter out stale fabricated overlays.
+ final ArrayList<FabricatedOverlayInfo> infos = new ArrayList<>(
+ mIdmapManager.getFabricatedOverlayInfos());
+ infos.removeIf(info -> !fabricatedPaths.contains(info.path));
+ return infos;
+ }
+
+ private boolean isPackageConfiguredMutable(@NonNull final AndroidPackage overlay) {
+ // TODO(162841629): Support overlay name in OverlayConfig
+ return mOverlayConfig.isMutable(overlay.getPackageName());
+ }
+
+ private int getPackageConfiguredPriority(@NonNull final AndroidPackage overlay) {
+ // TODO(162841629): Support overlay name in OverlayConfig
+ return mOverlayConfig.getPriority(overlay.getPackageName());
+ }
+
+ private boolean isPackageConfiguredEnabled(@NonNull final AndroidPackage overlay) {
+ // TODO(162841629): Support overlay name in OverlayConfig
+ return mOverlayConfig.isEnabled(overlay.getPackageName());
+ }
+
+ Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+ @NonNull final OverlayIdentifier newParentOverlay, final int userId)
+ throws OperationFailedException {
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "setPriority overlay=" + overlay + " newParentOverlay="
+ + newParentOverlay + " userId=" + userId);
+ }
+
+ final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+ if (!overlayInfo.isMutable) {
+ // Ignore immutable overlays.
+ throw new OperationFailedException(
+ "cannot change priority of an immutable overlay package at runtime");
+ }
+
+ if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
+ return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ }
+ return Optional.empty();
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ throw new OperationFailedException("failed to update settings", e);
+ }
+ }
+
+ Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
final int userId) throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
- }
+ try{
+ if (DEBUG) {
+ Slog.d(TAG, "setHighestPriority overlay=" + overlay + " userId=" + userId);
+ }
- if (!isPackageConfiguredMutable(packageName)) {
- throw new OperationFailedException(String.format(
- "overlay package %s user %d is not updatable", packageName, userId));
- }
+ final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+ if (!overlayInfo.isMutable) {
+ // Ignore immutable overlays.
+ throw new OperationFailedException(
+ "cannot change priority of an immutable overlay package at runtime");
+ }
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- throw new OperationFailedException(String.format(
- "failed to find overlay package %s for user %d", packageName, userId));
+ if (mSettings.setHighestPriority(overlay, userId)) {
+ return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ }
+ return Set.of();
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ throw new OperationFailedException("failed to update settings", e);
}
-
- if (mSettings.setHighestPriority(packageName, userId)) {
- return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
- }
- return Optional.empty();
}
- Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId)
- throws OperationFailedException {
- if (DEBUG) {
- Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
- }
+ Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+ final int userId) throws OperationFailedException {
+ try{
+ if (DEBUG) {
+ Slog.d(TAG, "setLowestPriority packageName=" + overlay + " userId=" + userId);
+ }
- if (!isPackageConfiguredMutable(packageName)) {
- throw new OperationFailedException(String.format(
- "overlay package %s user %d is not updatable", packageName, userId));
- }
+ final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+ if (!overlayInfo.isMutable) {
+ // Ignore immutable overlays.
+ throw new OperationFailedException(
+ "cannot change priority of an immutable overlay package at runtime");
+ }
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
- if (overlayPackage == null) {
- throw new OperationFailedException(String.format(
- "failed to find overlay package %s for user %d", packageName, userId));
+ if (mSettings.setLowestPriority(overlay, userId)) {
+ return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ }
+ return Optional.empty();
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ throw new OperationFailedException("failed to update settings", e);
}
-
- if (mSettings.setLowestPriority(packageName, userId)) {
- return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
- }
- return Optional.empty();
}
void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
@@ -693,9 +688,14 @@
return mDefaultOverlays;
}
- void removeIdmapForOverlay(String packageName, int userId) {
- final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
- removeIdmapIfPossible(oi);
+ void removeIdmapForOverlay(OverlayIdentifier overlay, int userId)
+ throws OperationFailedException {
+ try {
+ final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
+ removeIdmapIfPossible(oi);
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ throw new OperationFailedException("failed to update settings", e);
+ }
}
OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
@@ -709,7 +709,11 @@
if (!oi.isEnabled()) {
continue;
}
- paths.addApkPath(oi.baseCodePath);
+ if (oi.isFabricated()) {
+ paths.addNonApkPath(oi.baseCodePath);
+ } else {
+ paths.addApkPath(oi.baseCodePath);
+ }
}
return paths.build();
}
@@ -717,49 +721,53 @@
/**
* Returns true if the settings/state was modified, false otherwise.
*/
- private boolean updateState(@NonNull final String targetPackageName,
- @NonNull final String overlayPackageName, final int userId, final int flags)
- throws OverlayManagerSettings.BadKeyException {
+ private boolean updateState(@NonNull final CriticalOverlayInfo info,
+ final int userId, final int flags) throws OverlayManagerSettings.BadKeyException {
+ final OverlayIdentifier overlay = info.getOverlayIdentifier();
+ final AndroidPackage targetPackage = mPackageManager.getPackageForUser(
+ info.getTargetPackageName(), userId);
+ final AndroidPackage overlayPackage = mPackageManager.getPackageForUser(
+ info.getPackageName(), userId);
- final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
- final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
- userId);
+ boolean modified = false;
+ if (overlayPackage == null) {
+ removeIdmapIfPossible(mSettings.getOverlayInfo(overlay, userId));
+ return mSettings.remove(overlay, userId);
+ }
+
+ modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
+ if (!info.isFabricated()) {
+ modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
+ }
// Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
// layers.
- boolean modified = false;
- if (targetPackage != null && overlayPackage != null
- && !("android".equals(targetPackageName)
- && !isPackageConfiguredMutable(overlayPackageName))) {
- modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+ final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId);
+ if (targetPackage != null && !("android".equals(info.getTargetPackageName())
+ && !isPackageConfiguredMutable(overlayPackage))) {
+ modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage,
+ updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), userId);
}
- if (overlayPackage != null) {
- modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
- overlayPackage.applicationInfo.getBaseCodePath());
- modified |= mSettings.setCategory(overlayPackageName, userId,
- overlayPackage.overlayCategory);
- }
-
- final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
- final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
+ final @OverlayInfo.State int currentState = mSettings.getState(overlay, userId);
+ final @OverlayInfo.State int newState = calculateNewState(updatedOverlayInfo, targetPackage,
userId, flags);
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackageName, userId,
+ overlay, userId,
OverlayInfo.stateToString(currentState),
OverlayInfo.stateToString(newState)));
}
- modified |= mSettings.setState(overlayPackageName, userId, newState);
+ modified |= mSettings.setState(overlay, userId, newState);
}
+
return modified;
}
- private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
- @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
+ private @OverlayInfo.State int calculateNewState(@NonNull final OverlayInfo info,
+ @Nullable final AndroidPackage targetPackage, final int userId, final int flags)
throws OverlayManagerSettings.BadKeyException {
-
if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
return STATE_TARGET_IS_BEING_REPLACED;
}
@@ -768,20 +776,15 @@
return STATE_OVERLAY_IS_BEING_REPLACED;
}
- // assert expectation on overlay package: can only be null if the flags are used
- if (DEBUG && overlayPackage == null) {
- throw new IllegalArgumentException("null overlay package not compatible with no flags");
- }
-
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
- if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
+ if (!mIdmapManager.idmapExists(info)) {
return STATE_NO_IDMAP;
}
- final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
+ final boolean enabled = mSettings.getEnabled(info.getOverlayIdentifier(), userId);
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
@@ -810,7 +813,7 @@
final int[] userIds = mSettings.getUsers();
for (int userId : userIds) {
try {
- final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
+ final OverlayInfo tmp = mSettings.getOverlayInfo(oi.getOverlayIdentifier(), userId);
if (tmp != null && tmp.isEnabled()) {
// someone is still using the idmap file -> we cannot remove it
return;
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 0613dff..e3e0906 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -21,31 +21,32 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Stream;
/**
@@ -68,34 +69,45 @@
*/
private final ArrayList<SettingsItem> mItems = new ArrayList<>();
- void init(@NonNull final String packageName, final int userId,
+ @NonNull
+ OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
@NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
- @Nullable String overlayCategory) {
- remove(packageName, userId);
- insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
- baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
- overlayCategory));
+ @Nullable String overlayCategory, boolean isFabricated) {
+ remove(overlay, userId);
+ final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName,
+ targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled,
+ isMutable, priority, overlayCategory, isFabricated);
+ insert(item);
+ return item.getOverlayInfo();
}
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean remove(@NonNull final String packageName, final int userId) {
- final int idx = select(packageName, userId);
+ boolean remove(@NonNull final OverlayIdentifier overlay, final int userId) {
+ final int idx = select(overlay, userId);
if (idx < 0) {
return false;
}
-
mItems.remove(idx);
return true;
}
- @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+ @NonNull OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId)
throws BadKeyException {
- final int idx = select(packageName, userId);
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
+ }
+ return mItems.get(idx).getOverlayInfo();
+ }
+
+ @Nullable
+ OverlayInfo getNullableOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) {
+ final int idx = select(overlay, userId);
+ if (idx < 0) {
+ return null;
}
return mItems.get(idx).getOverlayInfo();
}
@@ -103,28 +115,29 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setBaseCodePath(@NonNull final String packageName, final int userId,
+ boolean setBaseCodePath(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String path) throws BadKeyException {
- final int idx = select(packageName, userId);
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setBaseCodePath(path);
}
- boolean setCategory(@NonNull final String packageName, final int userId,
+ boolean setCategory(@NonNull final OverlayIdentifier overlay, final int userId,
@Nullable String category) throws BadKeyException {
- final int idx = select(packageName, userId);
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setCategory(category);
}
- boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
- final int idx = select(packageName, userId);
+ boolean getEnabled(@NonNull final OverlayIdentifier overlay, final int userId)
+ throws BadKeyException {
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).isEnabled();
}
@@ -132,20 +145,20 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
- throws BadKeyException {
- final int idx = select(packageName, userId);
+ boolean setEnabled(@NonNull final OverlayIdentifier overlay, final int userId,
+ final boolean enable) throws BadKeyException {
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setEnabled(enable);
}
- @OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
+ @OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId)
throws BadKeyException {
- final int idx = select(packageName, userId);
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).getState();
}
@@ -153,11 +166,11 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setState(@NonNull final String packageName, final int userId,
+ boolean setState(@NonNull final OverlayIdentifier overlay, final int userId,
final @OverlayInfo.State int state) throws BadKeyException {
- final int idx = select(packageName, userId);
+ final int idx = select(overlay, userId);
if (idx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
return mItems.get(idx).setState(state);
}
@@ -166,53 +179,82 @@
final int userId) {
// Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
// ignored in OverlayManagerService.
- return selectWhereTarget(targetPackageName, userId)
- .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
- .map(SettingsItem::getOverlayInfo)
- .collect(Collectors.toList());
+ final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
+ items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+ return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
}
ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
// Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
// ignored in OverlayManagerService.
- return selectWhereUser(userId)
- .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
- .map(SettingsItem::getOverlayInfo)
- .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
- Collectors.toList()));
+ final List<SettingsItem> items = selectWhereUser(userId);
+ items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+
+ final ArrayMap<String, List<OverlayInfo>> targetInfos = new ArrayMap<>();
+ for (int i = 0, n = items.size(); i < n; i++) {
+ final SettingsItem item = items.get(i);
+ targetInfos.computeIfAbsent(item.mTargetPackageName, (String) -> new ArrayList<>())
+ .add(item.getOverlayInfo());
+ }
+ return targetInfos;
+ }
+
+ Set<String> getAllBaseCodePaths() {
+ final Set<String> paths = new ArraySet<>();
+ mItems.forEach(item -> paths.add(item.mBaseCodePath));
+ return paths;
+ }
+
+ @NonNull
+ List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
+ return removeIf(info -> (predicate.test(info) && info.userId == userId));
+ }
+
+ @NonNull
+ List<OverlayInfo> removeIf(final @NonNull Predicate<OverlayInfo> predicate) {
+ List<OverlayInfo> removed = null;
+ for (int i = mItems.size() - 1; i >= 0; i--) {
+ final OverlayInfo info = mItems.get(i).getOverlayInfo();
+ if (predicate.test(info)) {
+ mItems.remove(i);
+ removed = CollectionUtils.add(removed, info);
+ }
+ }
+ return CollectionUtils.emptyIfNull(removed);
}
int[] getUsers() {
return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray();
}
+ private static boolean isImmutableFrameworkOverlay(@NonNull SettingsItem item) {
+ return !item.isMutable() && "android".equals(item.getTargetPackageName());
+ }
+
/**
* Returns true if the settings were modified, false if they remain the same.
*/
boolean removeUser(final int userId) {
- boolean removed = false;
- for (int i = 0; i < mItems.size(); i++) {
- final SettingsItem item = mItems.get(i);
+ return mItems.removeIf(item -> {
if (item.getUserId() == userId) {
if (DEBUG) {
- Slog.d(TAG, "Removing overlay " + item.mPackageName + " for user " + userId
+ Slog.d(TAG, "Removing overlay " + item.mOverlay + " for user " + userId
+ " from settings because user was removed");
}
- mItems.remove(i);
- removed = true;
- i--;
+ return true;
}
- }
- return removed;
+ return false;
+ });
}
/**
* Reassigns the priority of an overlay maintaining the values of the overlays other settings.
*/
- void setPriority(@NonNull final String packageName, final int userId, final int priority) {
- final int moveIdx = select(packageName, userId);
+ void setPriority(@NonNull final OverlayIdentifier overlay, final int userId,
+ final int priority) throws BadKeyException {
+ final int moveIdx = select(overlay, userId);
if (moveIdx < 0) {
- throw new BadKeyException(packageName, userId);
+ throw new BadKeyException(overlay, userId);
}
final SettingsItem itemToMove = mItems.get(moveIdx);
@@ -224,17 +266,17 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setPriority(@NonNull final String packageName,
- @NonNull final String newParentPackageName, final int userId) {
- if (packageName.equals(newParentPackageName)) {
+ boolean setPriority(@NonNull final OverlayIdentifier overlay,
+ @NonNull final OverlayIdentifier newOverlay, final int userId) {
+ if (overlay.equals(newOverlay)) {
return false;
}
- final int moveIdx = select(packageName, userId);
+ final int moveIdx = select(overlay, userId);
if (moveIdx < 0) {
return false;
}
- final int parentIdx = select(newParentPackageName, userId);
+ final int parentIdx = select(newOverlay, userId);
if (parentIdx < 0) {
return false;
}
@@ -248,7 +290,7 @@
}
mItems.remove(moveIdx);
- final int newParentIdx = select(newParentPackageName, userId) + 1;
+ final int newParentIdx = select(newOverlay, userId) + 1;
mItems.add(newParentIdx, itemToMove);
return moveIdx != newParentIdx;
}
@@ -256,8 +298,8 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setLowestPriority(@NonNull final String packageName, final int userId) {
- final int idx = select(packageName, userId);
+ boolean setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+ final int idx = select(overlay, userId);
if (idx <= 0) {
// If the item doesn't exist or is already the lowest, don't change anything.
return false;
@@ -272,8 +314,8 @@
/**
* Returns true if the settings were modified, false if they remain the same.
*/
- boolean setHighestPriority(@NonNull final String packageName, final int userId) {
- final int idx = select(packageName, userId);
+ boolean setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+ final int idx = select(overlay, userId);
// If the item doesn't exist or is already the highest, don't change anything.
if (idx < 0 || idx == mItems.size() - 1) {
@@ -297,7 +339,6 @@
break;
}
}
-
mItems.add(i + 1, item);
}
@@ -308,7 +349,12 @@
items = items.filter(item -> item.mUserId == dumpState.getUserId());
}
if (dumpState.getPackageName() != null) {
- items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName()));
+ items = items.filter(item -> item.mOverlay.getPackageName()
+ .equals(dumpState.getPackageName()));
+ }
+ if (dumpState.getOverlayName() != null) {
+ items = items.filter(item -> item.mOverlay.getOverlayName()
+ .equals(dumpState.getOverlayName()));
}
// display items
@@ -322,10 +368,11 @@
private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw,
@NonNull final SettingsItem item) {
- pw.println(item.mPackageName + ":" + item.getUserId() + " {");
+ pw.println(item.mOverlay + ":" + item.getUserId() + " {");
pw.increaseIndent();
- pw.println("mPackageName...........: " + item.mPackageName);
+ pw.println("mPackageName...........: " + item.mOverlay.getPackageName());
+ pw.println("mOverlayName...........: " + item.mOverlay.getOverlayName());
pw.println("mUserId................: " + item.getUserId());
pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
@@ -335,6 +382,7 @@
pw.println("mIsMutable.............: " + item.isMutable());
pw.println("mPriority..............: " + item.mPriority);
pw.println("mCategory..............: " + item.mCategory);
+ pw.println("mIsFabricated..........: " + item.mIsFabricated);
pw.decreaseIndent();
pw.println("}");
@@ -344,7 +392,10 @@
@NonNull final SettingsItem item, @NonNull final String field) {
switch (field) {
case "packagename":
- pw.println(item.mPackageName);
+ pw.println(item.mOverlay.getPackageName());
+ break;
+ case "overlayname":
+ pw.println(item.mOverlay.getOverlayName());
break;
case "userid":
pw.println(item.mUserId);
@@ -392,6 +443,7 @@
private static final String ATTR_BASE_CODE_PATH = "baseCodePath";
private static final String ATTR_IS_ENABLED = "isEnabled";
private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_OVERLAY_NAME = "overlayName";
private static final String ATTR_STATE = "state";
private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName";
@@ -400,30 +452,26 @@
private static final String ATTR_CATEGORY = "category";
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_VERSION = "version";
+ private static final String ATTR_IS_FABRICATED = "fabricated";
@VisibleForTesting
static final int CURRENT_VERSION = 4;
public static void restore(@NonNull final ArrayList<SettingsItem> table,
@NonNull final InputStream is) throws IOException, XmlPullParserException {
+ table.clear();
+ final TypedXmlPullParser parser = Xml.resolvePullParser(is);
+ XmlUtils.beginDocument(parser, TAG_OVERLAYS);
+ final int version = parser.getAttributeInt(null, ATTR_VERSION);
+ if (version != CURRENT_VERSION) {
+ upgrade(version);
+ }
- {
- table.clear();
- final TypedXmlPullParser parser = Xml.resolvePullParser(is);
- XmlUtils.beginDocument(parser, TAG_OVERLAYS);
- int version = parser.getAttributeInt(null, ATTR_VERSION);
- if (version != CURRENT_VERSION) {
- upgrade(version);
- }
- int depth = parser.getDepth();
-
- while (XmlUtils.nextElementWithin(parser, depth)) {
- switch (parser.getName()) {
- case TAG_ITEM:
- final SettingsItem item = restoreRow(parser, depth + 1);
- table.add(item);
- break;
- }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_ITEM.equals(parser.getName())) {
+ final SettingsItem item = restoreRow(parser, depth + 1);
+ table.add(item);
}
}
}
@@ -447,7 +495,9 @@
private static SettingsItem restoreRow(@NonNull final TypedXmlPullParser parser,
final int depth) throws IOException, XmlPullParserException {
- final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME);
+ final OverlayIdentifier overlay = new OverlayIdentifier(
+ XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME),
+ XmlUtils.readStringAttribute(parser, ATTR_OVERLAY_NAME));
final int userId = parser.getAttributeInt(null, ATTR_USER_ID);
final String targetPackageName = XmlUtils.readStringAttribute(parser,
ATTR_TARGET_PACKAGE_NAME);
@@ -459,9 +509,11 @@
final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false);
final int priority = parser.getAttributeInt(null, ATTR_PRIORITY);
final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
+ final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED,
+ false);
- return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
- baseCodePath, state, isEnabled, !isStatic, priority, category);
+ return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName,
+ baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated);
}
public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -484,7 +536,8 @@
private static void persistRow(@NonNull final TypedXmlSerializer xml,
@NonNull final SettingsItem item) throws IOException {
xml.startTag(null, TAG_ITEM);
- XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName);
+ XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mOverlay.getPackageName());
+ XmlUtils.writeStringAttribute(xml, ATTR_OVERLAY_NAME, item.mOverlay.getOverlayName());
xml.attributeInt(null, ATTR_USER_ID, item.mUserId);
XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName);
XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME,
@@ -495,13 +548,14 @@
XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
xml.attributeInt(null, ATTR_PRIORITY, item.mPriority);
XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
+ XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated);
xml.endTag(null, TAG_ITEM);
}
}
private static final class SettingsItem {
private final int mUserId;
- private final String mPackageName;
+ private final OverlayIdentifier mOverlay;
private final String mTargetPackageName;
private final String mTargetOverlayableName;
private String mBaseCodePath;
@@ -511,13 +565,15 @@
private boolean mIsMutable;
private int mPriority;
private String mCategory;
+ private boolean mIsFabricated;
- SettingsItem(@NonNull final String packageName, final int userId,
+ SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId,
@NonNull final String targetPackageName,
@Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
final @OverlayInfo.State int state, final boolean isEnabled,
- final boolean isMutable, final int priority, @Nullable String category) {
- mPackageName = packageName;
+ final boolean isMutable, final int priority, @Nullable String category,
+ final boolean isFabricated) {
+ mOverlay = overlay;
mUserId = userId;
mTargetPackageName = targetPackageName;
mTargetOverlayableName = targetOverlayableName;
@@ -528,6 +584,7 @@
mCache = null;
mIsMutable = isMutable;
mPriority = priority;
+ mIsFabricated = isFabricated;
}
private String getTargetPackageName() {
@@ -596,8 +653,9 @@
private OverlayInfo getOverlayInfo() {
if (mCache == null) {
- mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
- mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsMutable);
+ mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(),
+ mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath,
+ mState, mUserId, mPriority, mIsMutable, mIsFabricated);
}
return mCache;
}
@@ -620,30 +678,40 @@
}
}
- private int select(@NonNull final String packageName, final int userId) {
+ private int select(@NonNull final OverlayIdentifier overlay, final int userId) {
final int n = mItems.size();
for (int i = 0; i < n; i++) {
final SettingsItem item = mItems.get(i);
- if (item.mUserId == userId && item.mPackageName.equals(packageName)) {
+ if (item.mUserId == userId && item.mOverlay.equals(overlay)) {
return i;
}
}
return -1;
}
- private Stream<SettingsItem> selectWhereUser(final int userId) {
- return mItems.stream().filter(item -> item.mUserId == userId);
+ private List<SettingsItem> selectWhereUser(final int userId) {
+ final List<SettingsItem> selectedItems = new ArrayList<>();
+ CollectionUtils.addIf(mItems, selectedItems, i -> i.mUserId == userId);
+ return selectedItems;
}
- private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+ private List<SettingsItem> selectWhereOverlay(@NonNull final String packageName,
final int userId) {
- return selectWhereUser(userId)
- .filter(item -> item.getTargetPackageName().equals(targetPackageName));
+ final List<SettingsItem> items = selectWhereUser(userId);
+ items.removeIf(i -> !i.mOverlay.getPackageName().equals(packageName));
+ return items;
}
- static final class BadKeyException extends RuntimeException {
- BadKeyException(@NonNull final String packageName, final int userId) {
- super("Bad key mPackageName=" + packageName + " mUserId=" + userId);
+ private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+ final int userId) {
+ final List<SettingsItem> items = selectWhereUser(userId);
+ items.removeIf(i -> !i.getTargetPackageName().equals(targetPackageName));
+ return items;
+ }
+
+ static final class BadKeyException extends Exception {
+ BadKeyException(@NonNull final OverlayIdentifier overlay, final int userId) {
+ super("Bad key '" + overlay + "' for user " + userId );
}
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bf99bd6..0b52c2e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -19,20 +19,28 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.om.FabricatedOverlay;
import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.os.Binder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.util.TypedValue;
+import com.android.internal.util.ArrayUtils;
+
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -72,6 +80,8 @@
return runSetPriority();
case "lookup":
return runLookup();
+ case "fabricate":
+ return runFabricate();
default:
return handleDefaultCommands(cmd);
}
@@ -89,35 +99,36 @@
out.println("Overlay manager (overlay) commands:");
out.println(" help");
out.println(" Print this help text.");
- out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
+ out.println(" dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
out.println(" Print debugging information about the overlay manager.");
- out.println(" With optional parameter PACKAGE, limit output to the specified");
- out.println(" package. With optional parameter FIELD, limit output to");
+ out.println(" With optional parameters PACKAGE and NAME, limit output to the specified");
+ out.println(" overlay or target. With optional parameter FIELD, limit output to");
out.println(" the value of that SettingsItem field. Field names are");
out.println(" case insensitive and out.println the m prefix can be omitted,");
out.println(" so the following are equivalent: mState, mstate, State, state.");
- out.println(" list [--user USER_ID] [PACKAGE]");
+ out.println(" list [--user USER_ID] [PACKAGE[:NAME]]");
out.println(" Print information about target and overlay packages.");
out.println(" Overlay packages are printed in priority order. With optional");
- out.println(" parameter PACKAGE, limit output to the specified package.");
- out.println(" enable [--user USER_ID] PACKAGE");
- out.println(" Enable overlay package PACKAGE.");
- out.println(" disable [--user USER_ID] PACKAGE");
- out.println(" Disable overlay package PACKAGE.");
+ out.println(" parameters PACKAGE and NAME, limit output to the specified overlay or");
+ out.println(" target.");
+ out.println(" enable [--user USER_ID] PACKAGE[:NAME]");
+ out.println(" Enable overlay within or owned by PACKAGE with optional unique NAME.");
+ out.println(" disable [--user USER_ID] PACKAGE[:NAME]");
+ out.println(" Disable overlay within or owned by PACKAGE with optional unique NAME.");
out.println(" enable-exclusive [--user USER_ID] [--category] PACKAGE");
- out.println(" Enable overlay package PACKAGE and disable all other overlays for");
- out.println(" its target package. If the --category option is given, only disables");
+ out.println(" Enable overlay within or owned by PACKAGE and disable all other overlays");
+ out.println(" for its target package. If the --category option is given, only disables");
out.println(" other overlays in the same category.");
out.println(" set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
- out.println(" Change the priority of the overlay PACKAGE to be just higher than");
- out.println(" the priority of PACKAGE_PARENT If PARENT is the special keyword");
+ out.println(" Change the priority of the overlay to be just higher than");
+ out.println(" the priority of PARENT If PARENT is the special keyword");
out.println(" 'lowest', change priority of PACKAGE to the lowest priority.");
out.println(" If PARENT is the special keyword 'highest', change priority of");
out.println(" PACKAGE to the highest priority.");
out.println(" lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
out.println(" Load a package and print the value of a given resource");
out.println(" applying the current configuration and enabled overlays.");
- out.println(" For a more fine-grained alernative, use 'idmap2 lookup'.");
+ out.println(" For a more fine-grained alternative, use 'idmap2 lookup'.");
}
private int runList() throws RemoteException {
@@ -192,7 +203,7 @@
status = "---";
break;
}
- out.println(String.format("%s %s", status, oi.packageName));
+ out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
}
private int runEnableDisable(final boolean enable) throws RemoteException {
@@ -211,8 +222,88 @@
}
}
- final String packageName = getNextArgRequired();
- return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
+ final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
+ mInterface.commit(new OverlayManagerTransaction.Builder()
+ .setEnabled(overlay, enable, userId)
+ .build());
+ return 0;
+ }
+
+ private int runFabricate() throws RemoteException {
+ final PrintWriter err = getErrPrintWriter();
+ if (Binder.getCallingUid() != Process.ROOT_UID) {
+ err.println("Error: must be root to fabricate overlays through the shell");
+ return 1;
+ }
+
+ int userId = UserHandle.USER_SYSTEM;
+ String targetPackage = "";
+ String targetOverlayable = "";
+ String name = "";
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ case "--target":
+ targetPackage = getNextArgRequired();
+ break;
+ case "--target-name":
+ targetOverlayable = getNextArgRequired();
+ break;
+ case "--name":
+ name = getNextArgRequired();
+ break;
+ default:
+ err.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ if (name.isEmpty()) {
+ err.println("Error: Missing required arg '--name'");
+ return 1;
+ }
+
+ if (targetPackage.isEmpty()) {
+ err.println("Error: Missing required arg '--target'");
+ return 1;
+ }
+
+ final String resourceName = getNextArgRequired();
+ final String typeStr = getNextArgRequired();
+ final int type;
+ if (typeStr.startsWith("0x")) {
+ type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
+ } else {
+ type = Integer.parseUnsignedInt(typeStr);
+ }
+ final String dataStr = getNextArgRequired();
+ final int data;
+ if (dataStr.startsWith("0x")) {
+ data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
+ } else {
+ data = Integer.parseUnsignedInt(dataStr);
+ }
+
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ err.println("Error: failed to get package manager");
+ return 1;
+ }
+
+ final String overlayPackageName = "com.android.shell";
+ final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+ overlayPackageName, name, targetPackage)
+ .setTargetOverlayable(targetOverlayable)
+ .setResourceValue(resourceName, type, data)
+ .build();
+
+ mInterface.commit(new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .build());
+ return 0;
}
private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/om/PackageManagerHelper.java b/services/core/java/com/android/server/om/PackageManagerHelper.java
index b1a8b4e..750f5c3 100644
--- a/services/core/java/com/android/server/om/PackageManagerHelper.java
+++ b/services/core/java/com/android/server/om/PackageManagerHelper.java
@@ -22,10 +22,15 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +41,33 @@
* @hide
*/
interface PackageManagerHelper {
+
+ /**
+ * Initializes the helper for the user. This only needs to be invoked one time before
+ * packages of this user are queried.
+ * @param userId the user id to initialize
+ * @return a map of package name to all packages installed in the user
+ */
+ @NonNull
+ ArrayMap<String, AndroidPackage> initializeForUser(final int userId);
+
+ /**
+ * Retrieves the package information if it is installed for the user.
+ */
+ @Nullable
+ AndroidPackage getPackageForUser(@NonNull final String packageName, final int userId);
+
+ /**
+ * Returns whether the package is an instant app.
+ */
+ boolean isInstantApp(@NonNull final String packageName, final int userId);
+
+ /**
+ * @see PackageManager#getPackagesForUid(int)
+ */
+ @Nullable
+ String[] getPackagesForUid(int uid);
+
/**
* @return true if the target package has declared an overlayable
*/
@@ -64,11 +96,6 @@
Map<String, Map<String, String>> getNamedActors();
/**
- * @see PackageManagerInternal#getOverlayPackages(int)
- */
- List<PackageInfo> getOverlayPackages(int userId);
-
- /**
* Read from the APK and AndroidManifest of a package to return the overlayable defined for
* a given name.
*
@@ -80,19 +107,6 @@
throws IOException;
/**
- * @see PackageManager#getPackagesForUid(int)
- */
- @Nullable
- String[] getPackagesForUid(int uid);
-
- /**
- * @param userId user to filter package visibility by
- * @see PackageManager#getPackageInfo(String, int)
- */
- @Nullable
- PackageInfo getPackageInfo(@NonNull String packageName, int userId);
-
- /**
* @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
* in the system returns {@link PackageManager#SIGNATURE_MATCH}
*/
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index de85d9e..f31d1da 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -43,6 +43,7 @@
import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,12 @@
abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
/**
+ * Returns array of all staged sessions known to apexd.
+ */
+ @NonNull
+ abstract SparseArray<ApexSessionInfo> getSessions();
+
+ /**
* Submit a staged session to apex service. This causes the apex service to perform some initial
* verification and accept or reject the session. Submitting a session successfully is not
* enough for it to be activated at the next boot, the caller needs to call
@@ -691,6 +698,21 @@
}
@Override
+ SparseArray<ApexSessionInfo> getSessions() {
+ try {
+ final ApexSessionInfo[] sessions = waitForApexService().getSessions();
+ final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length);
+ for (int i = 0; i < sessions.length; i++) {
+ result.put(sessions[i].sessionId, sessions[i]);
+ }
+ return result;
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact apexservice", re);
+ throw new RuntimeException(re);
+ }
+ }
+
+ @Override
ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
@@ -1083,6 +1105,11 @@
}
@Override
+ SparseArray<ApexSessionInfo> getSessions() {
+ return new SparseArray<>(0);
+ }
+
+ @Override
ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
// trade-off worth doing to save boot time work.
int result = pm.performDexOptWithStatus(new DexoptOptions(
pkg,
- PackageManagerService.REASON_BOOT,
+ PackageManagerService.REASON_POST_BOOT,
DexoptOptions.DEXOPT_BOOT_COMPLETE));
if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 52fdc79..308e815 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -27,6 +27,8 @@
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -45,12 +47,20 @@
public class DataLoaderManagerService extends SystemService {
private static final String TAG = "DataLoaderManager";
private final Context mContext;
+ private final HandlerThread mThread;
+ private final Handler mHandler;
private final DataLoaderManagerBinderService mBinderService;
private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
public DataLoaderManagerService(Context context) {
super(context);
mContext = context;
+
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+
+ mHandler = new Handler(mThread.getLooper());
+
mBinderService = new DataLoaderManagerBinderService();
}
@@ -62,7 +72,7 @@
final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub {
@Override
public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params,
- IDataLoaderStatusListener listener) {
+ long bindDelayMs, IDataLoaderStatusListener listener) {
synchronized (mServiceConnections) {
if (mServiceConnections.get(dataLoaderId) != null) {
return true;
@@ -76,19 +86,21 @@
}
// Binds to the specific data loader service.
- DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId,
- listener);
+ final DataLoaderServiceConnection connection = new DataLoaderServiceConnection(
+ dataLoaderId, listener);
- Intent intent = new Intent();
+ final Intent intent = new Intent();
intent.setComponent(dataLoaderComponent);
- if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
- UserHandle.of(UserHandle.getCallingUserId()))) {
- Slog.e(TAG,
- "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId);
- mContext.unbindService(connection);
- return false;
- }
- return true;
+
+ return mHandler.postDelayed(() -> {
+ if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ mHandler, UserHandle.of(UserHandle.getCallingUserId()))) {
+ Slog.e(TAG,
+ "Failed to bind to: " + dataLoaderComponent + " for ID="
+ + dataLoaderId);
+ mContext.unbindService(connection);
+ }
+ }, bindDelayMs);
}
/**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f240d85..e91bb46 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -929,6 +929,7 @@
// Flag for bubble to make behaviour match documentLaunchMode=always.
intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true);
}
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1317,6 +1318,10 @@
mListeners.finishBroadcast();
}
super.onPackageAdded(packageName, uid);
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ pmi.registerInstalledLoadingProgressCallback(packageName,
+ new PackageLoadingProgressCallback(packageName, user),
+ user.getIdentifier());
}
@Override
@@ -1538,5 +1543,38 @@
checkCallbackCount();
}
}
+
+ class PackageLoadingProgressCallback extends
+ PackageManagerInternal.InstalledLoadingProgressCallback {
+ private String mPackageName;
+ private UserHandle mUser;
+
+ PackageLoadingProgressCallback(String packageName, UserHandle user) {
+ super(mCallbackHandler);
+ mPackageName = packageName;
+ mUser = user;
+ }
+
+ @Override
+ public void onLoadingProgressChanged(float progress) {
+ final int n = mListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+ BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+ if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
+ continue;
+ }
+ try {
+ listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress);
+ } catch (RemoteException re) {
+ Slog.d(TAG, "Callback failed ", re);
+ }
+ }
+ } finally {
+ mListeners.finishBroadcast();
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
boolean generateCompactDex = true;
switch (compilationReason) {
case PackageManagerService.REASON_FIRST_BOOT:
- case PackageManagerService.REASON_BOOT:
+ case PackageManagerService.REASON_BOOT_AFTER_OTA:
+ case PackageManagerService.REASON_POST_BOOT:
case PackageManagerService.REASON_INSTALL:
generateCompactDex = false;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2d393c0..2812830 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -295,23 +295,29 @@
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.isStaged()) {
- stagedSessionsToRestore.add(session.mStagedSession);
+ if (!session.isStaged()) {
+ continue;
+ }
+ StagingManager.StagedSession stagedSession = session.mStagedSession;
+ if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
+ && getSession(stagedSession.getParentSessionId()) == null) {
+ stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "An orphan staged session " + stagedSession.sessionId() + " is found, "
+ + "parent " + stagedSession.getParentSessionId() + " is missing");
+ continue;
+ }
+ if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted()
+ && !stagedSession.isInTerminalState()) {
+ // StagingManager.restoreSessions expects a list of committed, non-finalized
+ // parent staged sessions.
+ stagedSessionsToRestore.add(stagedSession);
}
}
}
- // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
+ // Don't hold mSessions lock when calling restoreSessions, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
- boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
- for (StagingManager.StagedSession session : stagedSessionsToRestore) {
- if (!session.isInTerminalState() && session.hasParentSessionId()
- && getSession(session.getParentSessionId()) == null) {
- session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "An orphan staged session " + session.sessionId() + " is found, "
- + "parent " + session.getParentSessionId() + " is missing");
- }
- mStagingManager.restoreSession(session, isDeviceUpgrading);
- }
+ // Note: restoreSessions mutates content of stagedSessionsToRestore.
+ mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading());
}
@GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c42569..0ce2673 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3778,7 +3778,9 @@
}
}
- if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
+ final long bindDelayMs = 0;
+ if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs,
+ statusListener)) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failed to initialize data loader");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0e6f557..7b9cf73 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -780,17 +780,18 @@
// Compilation reasons.
public static final int REASON_UNKNOWN = -1;
public static final int REASON_FIRST_BOOT = 0;
- public static final int REASON_BOOT = 1;
- public static final int REASON_INSTALL = 2;
- public static final int REASON_INSTALL_FAST = 3;
- public static final int REASON_INSTALL_BULK = 4;
- public static final int REASON_INSTALL_BULK_SECONDARY = 5;
- public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
- public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
- public static final int REASON_BACKGROUND_DEXOPT = 8;
- public static final int REASON_AB_OTA = 9;
- public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
- public static final int REASON_SHARED = 11;
+ public static final int REASON_BOOT_AFTER_OTA = 1;
+ public static final int REASON_POST_BOOT = 2;
+ public static final int REASON_INSTALL = 3;
+ public static final int REASON_INSTALL_FAST = 4;
+ public static final int REASON_INSTALL_BULK = 5;
+ public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+ public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+ public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+ public static final int REASON_BACKGROUND_DEXOPT = 9;
+ public static final int REASON_AB_OTA = 10;
+ public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+ public static final int REASON_SHARED = 12;
public static final int REASON_LAST = REASON_SHARED;
@@ -11637,10 +11638,7 @@
// first boot, as they do not have profile data.
boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
- // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
- boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
- if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+ if (!causeUpgrade && !causeFirstBoot) {
return;
}
@@ -11657,7 +11655,7 @@
final long startTime = System.nanoTime();
final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+ causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
false /* bootComplete */);
final int elapsedTimeSeconds =
@@ -26213,30 +26211,7 @@
@Override
public void setKeepUninstalledPackages(final 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));
- }
- }
- }
+ PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList);
}
@Override
@@ -27036,6 +27011,28 @@
}
@Override
+ public boolean registerInstalledLoadingProgressCallback(String packageName,
+ PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
+ final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
+ userId);
+ if (ps == null) {
+ return false;
+ }
+ if (!ps.isPackageLoading()) {
+ Slog.w(TAG,
+ "Failed registering loading progress callback. Package is fully loaded.");
+ return false;
+ }
+ if (mIncrementalManager == null) {
+ Slog.w(TAG,
+ "Failed registering loading progress callback. Incremental is not enabled");
+ return false;
+ }
+ return mIncrementalManager.registerLoadingProgressCallback(ps.getPathString(),
+ (IPackageLoadingProgressCallback) callback.getBinder());
+ }
+
+ @Override
public IncrementalStatesInfo getIncrementalStatesInfo(
@NonNull String packageName, int filterCallingUid, int userId) {
final PackageSetting ps = getPackageSettingForUser(packageName, filterCallingUid,
@@ -27712,6 +27709,43 @@
public DomainVerificationService.Connection getDomainVerificationConnection() {
return mDomainVerificationConnection;
}
+
+ @Override
+ public void setKeepUninstalledPackages(List<String> packageList) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.KEEP_UNINSTALLED_PACKAGES,
+ "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission");
+ Objects.requireNonNull(packageList);
+
+ setKeepUninstalledPackagesInternal(packageList);
+ }
+
+ 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));
+ }
+ }
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
// Names for compilation reasons.
public static final String REASON_STRINGS[] = {
"first-boot",
- "boot",
+ "boot-after-ota",
+ "post-boot",
"install",
"install-fast",
"install-bulk",
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 545567c..0a74032 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -50,8 +50,6 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IntArray;
@@ -63,6 +61,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
@@ -143,10 +142,16 @@
}
StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
+ this(context, packageParserSupplier, ApexManager.getInstance());
+ }
+
+ @VisibleForTesting
+ StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
+ ApexManager apexManager) {
mContext = context;
mPackageParserSupplier = packageParserSupplier;
- mApexManager = ApexManager.getInstance();
+ mApexManager = apexManager;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mPreRebootVerificationHandler = new PreRebootVerificationHandler(
BackgroundThread.get().getLooper());
@@ -354,11 +359,11 @@
}
// Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
- private void abortCheckpoint(int sessionId, String errorMsg) {
- String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
+ private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
+ boolean needsCheckpoint) {
Slog.e(TAG, failureReason);
try {
- if (supportsCheckpoint() && needsCheckpoint()) {
+ if (supportsCheckpoint && needsCheckpoint) {
// Store failure reason for next reboot
try (BufferedWriter writer =
new BufferedWriter(new FileWriter(mFailureReasonFile))) {
@@ -371,8 +376,9 @@
if (mApexManager.isApexSupported()) {
mApexManager.revertActiveSessions();
}
+
PackageHelper.getStorageManager().abortChanges(
- "StagingManager initiated", false /*retry*/);
+ "abort-staged-install", false /*retry*/);
}
} catch (Exception e) {
Slog.wtf(TAG, "Failed to abort checkpoint", e);
@@ -384,14 +390,6 @@
}
}
- private boolean supportsCheckpoint() throws RemoteException {
- return PackageHelper.getStorageManager().supportsCheckpoint();
- }
-
- private boolean needsCheckpoint() throws RemoteException {
- return PackageHelper.getStorageManager().needsCheckpoint();
- }
-
/**
* Utility function for extracting apex sessions out of multi-package/single session.
*/
@@ -517,96 +515,31 @@
}
}
- private void resumeSession(@NonNull StagedSession session)
- throws PackageManagerException {
+ private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
+ boolean needsCheckpoint) throws PackageManagerException {
Slog.d(TAG, "Resuming session " + session.sessionId());
final boolean hasApex = session.containsApexSession();
- ApexSessionInfo apexSessionInfo = null;
- if (hasApex) {
- // Check with apexservice whether the apex packages have been activated.
- apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId());
-
- // Prepare for logging a native crash during boot, if one occurred.
- if (apexSessionInfo != null && !TextUtils.isEmpty(
- apexSessionInfo.crashingNativeProcess)) {
- prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
- }
-
- if (apexSessionInfo != null && apexSessionInfo.isVerified) {
- // Session has been previously submitted to apexd, but didn't complete all the
- // pre-reboot verification, perhaps because the device rebooted in the meantime.
- // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
- // failed when not in checkpoint mode, hence it is being processed separately.
- Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to "
- + "be verified, resuming pre-reboot verification");
- mPreRebootVerificationHandler.startPreRebootVerification(session);
- return;
- }
- }
// Before we resume session, we check if revert is needed or not. Typically, we enter file-
// system checkpoint mode when we reboot first time in order to install staged sessions. We
// want to install staged sessions in this mode as rebooting now will revert user data. If
// something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
// have no effect on user data, so mark the sessions as failed instead.
- try {
- // If checkpoint is supported, then we only resume sessions if we are in checkpointing
- // mode. If not, we fail all sessions.
- if (supportsCheckpoint() && !needsCheckpoint()) {
- String revertMsg = "Reverting back to safe state. Marking "
- + session.sessionId() + " as failed.";
- final String reasonForRevert = getReasonForRevert();
- if (!TextUtils.isEmpty(reasonForRevert)) {
- revertMsg += " Reason for revert: " + reasonForRevert;
- }
- Slog.d(TAG, revertMsg);
- session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
- return;
+ // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
+ // If not, we fail all sessions.
+ if (supportsCheckpoint && !needsCheckpoint) {
+ String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
+ + " as failed.";
+ final String reasonForRevert = getReasonForRevert();
+ if (!TextUtils.isEmpty(reasonForRevert)) {
+ revertMsg += " Reason for revert: " + reasonForRevert;
}
- } catch (RemoteException e) {
- // Cannot continue staged install without knowing if fs-checkpoint is supported
- Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
- + session.sessionId(), e);
- // TODO: Mark all staged sessions together and reboot only once
- session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
- "Checkpoint support unknown. Aborting staged install.");
- if (hasApex) {
- mApexManager.revertActiveSessions();
- }
- mPowerManager.reboot("Checkpoint support unknown");
+ Slog.d(TAG, revertMsg);
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
return;
}
- // Check if apex packages in the session failed to activate
- if (hasApex) {
- if (apexSessionInfo == null) {
- final String errorMsg = "apexd did not know anything about a staged session "
- + "supposed to be activated";
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- if (isApexSessionFailed(apexSessionInfo)) {
- String errorMsg = "APEX activation failed. Check logcat messages from apexd "
- + "for more information.";
- if (!TextUtils.isEmpty(mNativeFailureReason)) {
- errorMsg = "Session reverted due to crashing native process: "
- + mNativeFailureReason;
- }
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
- // Apexd did not apply the session for some unknown reason. There is no
- // guarantee that apexd will install it next time. Safer to proactively mark
- // it as failed.
- final String errorMsg = "Staged session " + session.sessionId() + "at boot "
- + "didn't activate nor fail. Marking it as failed anyway.";
- throw new PackageManagerException(
- SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
- }
- }
-
// Handle apk and apk-in-apex installation
if (hasApex) {
checkInstallationOfApkInApexSuccessful(session);
@@ -622,28 +555,24 @@
Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
session.setSessionApplied();
if (hasApex) {
- try {
- if (supportsCheckpoint()) {
- // Store the session ID, which will be marked as successful by ApexManager
- // upon boot completion.
- synchronized (mSuccessfulStagedSessionIds) {
- mSuccessfulStagedSessionIds.add(session.sessionId());
- }
- } else {
- // Mark sessions as successful immediately on non-checkpointing devices.
- mApexManager.markStagedSessionSuccessful(session.sessionId());
+ if (supportsCheckpoint) {
+ // Store the session ID, which will be marked as successful by ApexManager upon
+ // boot completion.
+ synchronized (mSuccessfulStagedSessionIds) {
+ mSuccessfulStagedSessionIds.add(session.sessionId());
}
- } catch (RemoteException e) {
- Slog.w(TAG, "Checkpoint support unknown, marking session as successful "
- + "immediately.");
+ } else {
+ // Mark sessions as successful immediately on non-checkpointing devices.
mApexManager.markStagedSessionSuccessful(session.sessionId());
}
}
}
- void onInstallationFailure(StagedSession session, PackageManagerException e) {
+ void onInstallationFailure(StagedSession session, PackageManagerException e,
+ boolean supportsCheckpoint, boolean needsCheckpoint) {
session.setSessionFailed(e.error, e.getMessage());
- abortCheckpoint(session.sessionId(), e.getMessage());
+ abortCheckpoint("Failed to install sessionId: " + session.sessionId()
+ + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
// If checkpoint is not supported, we have to handle failure for one staged session.
if (!session.containsApexSession()) {
@@ -767,8 +696,13 @@
"Cannot stage session " + session.sessionId() + " with package name null");
}
- boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
- Context.STORAGE_SERVICE)).isCheckpointSupported();
+ boolean supportsCheckpoint;
+ try {
+ supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+ } catch (RemoteException e) {
+ throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+ "Can't query fs-checkpoint status : " + e);
+ }
final boolean isRollback = isRollback(session);
@@ -911,60 +845,166 @@
|| apexSessionInfo.isRevertFailed;
}
- void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) {
- if (session.hasParentSessionId()) {
- // Only parent sessions can be restored
- return;
+ private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
+ int j = sessions.size();
+ for (int i = 0; i < j; ) {
+ // Maintain following invariant:
+ // * elements at positions [0, i) should be kept
+ // * elements at positions [j, n) should be remove.
+ // * n = sessions.size()
+ StagedSession session = sessions.get(i);
+ if (session.isDestroyed()) {
+ // Device rebooted before abandoned session was cleaned up.
+ session.abandon();
+ StagedSession session2 = sessions.set(j - 1, session);
+ sessions.set(i, session2);
+ j--;
+ } else if (!session.isSessionReady()) {
+ // The framework got restarted before the pre-reboot verification could complete,
+ // restart the verification.
+ mPreRebootVerificationHandler.startPreRebootVerification(session);
+ StagedSession session2 = sessions.set(j - 1, session);
+ sessions.set(i, session2);
+ j--;
+ } else {
+ i++;
+ }
}
- // Store this parent session which will be used to check overlapping later
- createSession(session);
- // The preconditions used during pre-reboot verification might have changed when device
- // is upgrading. Updated staged sessions to activation failed before we resume the session.
- StagedSession sessionToResume = session;
- if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) {
- sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Build fingerprint has changed");
- return;
- }
- checkStateAndResume(sessionToResume);
+ // Delete last j elements.
+ sessions.subList(j, sessions.size()).clear();
}
- private void checkStateAndResume(@NonNull StagedSession session) {
- // Do not resume session if boot completed already
+ void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
+ // Do not resume sessions if boot completed already
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
return;
}
- if (!session.isCommitted()) {
- // Session hasn't been committed yet, ignore.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ // Quick check that PackageInstallerService gave us sessions we expected.
+ Preconditions.checkArgument(!session.hasParentSessionId(),
+ session.sessionId() + " is a child session");
+ Preconditions.checkArgument(session.isCommitted(),
+ session.sessionId() + " is not committed");
+ Preconditions.checkArgument(!session.isInTerminalState(),
+ session.sessionId() + " is in terminal state");
+ // Store this parent session which will be used to check overlapping later
+ createSession(session);
+ }
+
+ if (isDeviceUpgrading) {
+ // TODO(ioffe): check that corresponding apex sessions are failed.
+ // The preconditions used during pre-reboot verification might have changed when device
+ // is upgrading. Fail all the sessions and exit early.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Build fingerprint has changed");
+ }
return;
}
- // Check the state of the session and decide what to do next.
- if (session.isSessionFailed() || session.isSessionApplied()) {
- // Final states, nothing to do.
+
+ boolean needsCheckpoint = false;
+ boolean supportsCheckpoint = false;
+ try {
+ supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+ needsCheckpoint = PackageHelper.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);
+ }
+
+ if (sessions.size() > 1 && !supportsCheckpoint) {
+ throw new IllegalStateException("Detected multiple staged sessions on a device without "
+ + "fs-checkpoint support");
+ }
+
+ // Do a set of quick checks before resuming individual sessions:
+ // 1. Schedule a pre-reboot verification for non-ready sessions.
+ // 2. Abandon destroyed sessions.
+ handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
+
+ // 3. Check state of apex sessions is consistent. All non-applied sessions will be marked
+ // as failed.
+ final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
+ boolean hasFailedApexSession = false;
+ boolean hasAppliedApexSession = false;
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ if (!session.containsApexSession()) {
+ // At this point we are only interested in apex sessions.
+ continue;
+ }
+ final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
+ if (apexSession == null || apexSession.isUnknown) {
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
+ + "not know anything about a staged session supposed to be activated");
+ continue;
+ } else if (isApexSessionFailed(apexSession)) {
+ hasFailedApexSession = true;
+ String errorMsg = "APEX activation failed. Check logcat messages from apexd "
+ + "for more information.";
+ if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
+ prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
+ errorMsg = "Session reverted due to crashing native process: "
+ + apexSession.crashingNativeProcess;
+ }
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
+ continue;
+ } else if (apexSession.isActivated || apexSession.isSuccess) {
+ hasAppliedApexSession = true;
+ continue;
+ } else if (apexSession.isStaged) {
+ // Apexd did not apply the session for some unknown reason. There is no guarantee
+ // that apexd will install it next time. Safer to proactively mark it as failed.
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Staged session " + session.sessionId() + " at boot didn't activate nor "
+ + "fail. Marking it as failed anyway.");
+ } else {
+ Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
+ hasFailedApexSession = true;
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Impossible state");
+ }
+ }
+
+ if (hasAppliedApexSession && hasFailedApexSession) {
+ abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
+ needsCheckpoint);
return;
}
- if (session.isDestroyed()) {
- // Device rebooted before abandoned session was cleaned up.
- session.abandon();
+
+ if (hasFailedApexSession) {
+ // Either of those means that we failed at least one apex session, hence we should fail
+ // all other sessions.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
+ if (session.isSessionFailed()) {
+ // Session has been already failed in the loop above.
+ continue;
+ }
+ session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+ "Another apex session failed");
+ }
return;
}
- if (!session.isSessionReady()) {
- // The framework got restarted before the pre-reboot verification could complete,
- // restart the verification.
- mPreRebootVerificationHandler.startPreRebootVerification(session);
- } else {
- // Session had already being marked ready. Start the checks to verify if there is any
- // follow-up work.
+
+ // Time to resume sessions.
+ for (int i = 0; i < sessions.size(); i++) {
+ StagedSession session = sessions.get(i);
try {
- resumeSession(session);
+ resumeSession(session, supportsCheckpoint, needsCheckpoint);
} catch (PackageManagerException e) {
- onInstallationFailure(session, e);
+ onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
} catch (Exception e) {
Slog.e(TAG, "Staged install failed due to unhandled exception", e);
onInstallationFailure(session, new PackageManagerException(
SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Staged install failed due to unhandled exception: " + e));
+ "Staged install failed due to unhandled exception: " + e),
+ supportsCheckpoint, needsCheckpoint);
}
}
}
@@ -992,9 +1032,7 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
- mPreRebootVerificationHandler.readyToStart();
- BackgroundThread.getExecutor().execute(
- () -> logFailedApexSessionsIfNecessary());
+ onBootCompletedBroadcastReceived();
ctx.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
@@ -1002,6 +1040,12 @@
mFailureReasonFile.delete();
}
+ @VisibleForTesting
+ void onBootCompletedBroadcastReceived() {
+ mPreRebootVerificationHandler.readyToStart();
+ BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
+ }
+
private static class LocalIntentReceiverSync {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
@@ -1286,9 +1330,8 @@
private void handlePreRebootVerification_End(@NonNull StagedSession session) {
// Before marking the session as ready, start checkpoint service if available
try {
- IStorageManager storageManager = PackageHelper.getStorageManager();
- if (storageManager.supportsCheckpoint()) {
- storageManager.startCheckpoint(2);
+ if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+ PackageHelper.getStorageManager().startCheckpoint(2);
}
} catch (Exception e) {
// Failed to get hold of StorageManager
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
private static final int TRON_COMPILATION_REASON_ERROR = 0;
private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
- private static final int TRON_COMPILATION_REASON_BOOT = 3;
+ private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
private static final int TRON_COMPILATION_REASON_INSTALL = 4;
private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
private static final int
TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+ private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+ private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -618,7 +620,8 @@
case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
case "error" : return TRON_COMPILATION_REASON_ERROR;
case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
- case "boot" : return TRON_COMPILATION_REASON_BOOT;
+ case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+ case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
case "install" : return TRON_COMPILATION_REASON_INSTALL;
case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 0e88862..e05ef48 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,3 @@
-moltmann@google.com
zhanghai@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
@@ -7,5 +6,4 @@
per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
per-file DefaultPermissionGrantPolicy.java = patb@google.com
per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
-per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 30c334d..32bee58 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -38,6 +38,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.Objects;
+import java.util.Set;
/**
* Permission definition.
@@ -345,6 +346,14 @@
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
}
+ public boolean isKnownSigner() {
+ return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
+ }
+
+ public Set<String> getKnownCerts() {
+ return mPermissionInfo.knownCerts;
+ }
+
public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
if (!oldPackageName.equals(mPermissionInfo.packageName)) {
return;
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 aff87111..e486f08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2590,6 +2590,7 @@
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
+ ArraySet<String> isPrivilegedPermissionAllowlisted = null;
ArraySet<String> shouldGrantSignaturePermission = null;
ArraySet<String> shouldGrantInternalPermission = null;
final List<String> requestedPermissions = pkg.getRequestedPermissions();
@@ -2604,7 +2605,14 @@
if (permission == null) {
continue;
}
- if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission)
+ if (permission.isPrivileged()
+ && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+ if (isPrivilegedPermissionAllowlisted == null) {
+ isPrivilegedPermissionAllowlisted = new ArraySet<>();
+ }
+ isPrivilegedPermissionAllowlisted.add(permissionName);
+ }
+ if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
|| shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
if (shouldGrantSignaturePermission == null) {
shouldGrantSignaturePermission = new ArraySet<>();
@@ -2830,13 +2838,17 @@
if ((bp.isNormal() && shouldGrantNormalPermission)
|| (bp.isSignature()
- && ((shouldGrantSignaturePermission != null
- && shouldGrantSignaturePermission.contains(permName))
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantSignaturePermission,
+ permName)
|| ((bp.isDevelopment() || bp.isRole())
&& origState.isPermissionGranted(permName))))
|| (bp.isInternal()
- && ((shouldGrantInternalPermission != null
- && shouldGrantInternalPermission.contains(permName))
+ && (!bp.isPrivileged() || CollectionUtils.contains(
+ isPrivilegedPermissionAllowlisted, permName))
+ && (CollectionUtils.contains(shouldGrantInternalPermission,
+ permName)
|| ((bp.isDevelopment() || bp.isRole())
&& origState.isPermissionGranted(permName))))) {
// Grant an install permission.
@@ -3343,7 +3355,92 @@
return allowed;
}
- private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+ private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+ @NonNull PackageSetting packageSetting, @NonNull Permission permission) {
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+ return true;
+ }
+ final String packageName = pkg.getPackageName();
+ if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+ return true;
+ }
+ if (!pkg.isPrivileged()) {
+ return true;
+ }
+ if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) {
+ return true;
+ }
+ final String permissionName = permission.getName();
+ if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+ return true;
+ }
+ // Only enforce the allowlist on boot
+ if (!mSystemReady
+ // Updated system apps do not need to be allowlisted
+ && !packageSetting.getPkgState().isUpdatedSystemApp()) {
+ final ApexManager apexManager = ApexManager.getInstance();
+ final String containingApexPackageName =
+ apexManager.getActiveApexPackageNameContainingPackage(packageName);
+ final boolean isInUpdatedApex = containingApexPackageName != null
+ && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+ MATCH_ACTIVE_PACKAGE));
+ // Apps that are in updated apexs' do not need to be allowlisted
+ if (!isInUpdatedApex) {
+ // it's only a reportable violation if the permission isn't explicitly
+ // denied
+ if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+ return false;
+ }
+ Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+ + packageName + " (" + pkg.getPath()
+ + ") not in privapp-permissions allowlist");
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+ synchronized (mLock) {
+ if (mPrivappPermissionsViolations == null) {
+ mPrivappPermissionsViolations = new ArraySet<>();
+ }
+ mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+ + permissionName);
+ }
+ }
+ }
+ }
+ return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+ }
+
+ private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+ }
+ return CollectionUtils.contains(permissions, permission);
+ }
+
+ private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+ }
+ return CollectionUtils.contains(permissions, permission);
+ }
+
+ private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
@NonNull Permission bp) {
// expect single system package
String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
@@ -3373,8 +3470,7 @@
private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
@NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
boolean allowed = false;
- final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
- final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+ final boolean isPrivilegedPermission = bp.isPrivileged();
final boolean isOemPermission = bp.isOem();
if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
final String permissionName = bp.getName();
@@ -3386,19 +3482,18 @@
final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
permissionName)) {
- allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
- true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
+ allowed = (isPrivilegedPermission && disabledPkg.isPrivileged())
+ || (isOemPermission && canGrantOemPermission(disabledPkg,
permissionName));
}
} else {
- allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+ allowed = (isPrivilegedPermission && pkg.isPrivileged())
|| (isOemPermission && canGrantOemPermission(pkg, permissionName));
}
// In any case, don't grant a privileged permission to privileged vendor apps, if
// the permission's protectionLevel does not have the extra 'vendorPrivileged'
// flag.
- if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission
- && pkg.isVendor()) {
+ if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
Slog.w(TAG, "Permission " + permissionName
+ " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+ " because it isn't a 'vendorPrivileged' permission.");
@@ -3438,6 +3533,11 @@
// Any pre-installed system app is allowed to get this permission.
allowed = true;
}
+ if (!allowed && bp.isKnownSigner()) {
+ // If the permission is to be granted to a known signer then check if any of this
+ // app's signing certificates are in the trusted certificate digest Set.
+ allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+ }
// Deferred to be checked under permission data lock inside restorePermissionState().
//if (!allowed && bp.isDevelopment()) {
// // For development permissions, a development permission
@@ -3536,90 +3636,6 @@
return mPackageManagerInt.getPackageSetting(sourcePackageName);
}
- private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
- boolean isUpdatedSystemApp, @NonNull Permission permission) {
- if (!pkg.isPrivileged()) {
- return false;
- }
- final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
- permission.getPackageName());
- if (!isPlatformPermission) {
- return true;
- }
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
- return true;
- }
- final String permissionName = permission.getName();
- if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
- return true;
- }
- // Only enforce the allowlist on boot
- if (!mSystemReady
- // Updated system apps do not need to be allowlisted
- && !isUpdatedSystemApp) {
- final ApexManager apexManager = ApexManager.getInstance();
- final String packageName = pkg.getPackageName();
- final String containingApexPackageName =
- apexManager.getActiveApexPackageNameContainingPackage(packageName);
- final boolean isInUpdatedApex = containingApexPackageName != null
- && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
- MATCH_ACTIVE_PACKAGE));
- // Apps that are in updated apexs' do not need to be allowlisted
- if (!isInUpdatedApex) {
- // it's only a reportable violation if the permission isn't explicitly
- // denied
- if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
- return false;
- }
- Slog.w(TAG, "Privileged permission " + permissionName + " for package "
- + packageName + " (" + pkg.getPath()
- + ") not in privapp-permissions allowlist");
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- synchronized (mLock) {
- if (mPrivappPermissionsViolations == null) {
- mPrivappPermissionsViolations = new ArraySet<>();
- }
- mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
- + permissionName);
- }
- }
- }
- }
- return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
- }
-
- private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
- @NonNull String permission) {
- final SystemConfig systemConfig = SystemConfig.getInstance();
- final Set<String> permissions;
- if (pkg.isVendor()) {
- permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
- } else {
- permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
- }
- return permissions != null && permissions.contains(permission);
- }
-
- private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
- @NonNull String permission) {
- final SystemConfig systemConfig = SystemConfig.getInstance();
- final Set<String> permissions;
- if (pkg.isVendor()) {
- permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
- } else {
- permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
- }
- return permissions != null && permissions.contains(permission);
- }
-
private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
if (!pkg.isOem()) {
return false;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index 36efb39..080de73 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -21,11 +21,8 @@
import android.compat.annotation.EnabledSince;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedIntentInfo;
-import android.os.Binder;
import android.os.Build;
import android.util.ArraySet;
import android.util.Patterns;
@@ -36,19 +33,31 @@
import java.util.List;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class DomainVerificationCollector {
+ // The default domain name matcher doesn't account for wildcards, so prefix with *.
+ private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
+ Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
+
@NonNull
private final PlatformCompat mPlatformCompat;
@NonNull
private final SystemConfig mSystemConfig;
+ @NonNull
+ private final Matcher mDomainMatcher;
+
public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
@NonNull SystemConfig systemConfig) {
mPlatformCompat = platformCompat;
mSystemConfig = systemConfig;
+
+ // Cache the matcher to avoid calling into native on each check
+ mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher("");
}
/**
@@ -144,7 +153,10 @@
if (intent.handlesWebUris(false)) {
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
- domains.add(intent.getDataAuthority(index).getHost());
+ String host = intent.getDataAuthority(index).getHost();
+ if (isValidHost(host)) {
+ domains.add(host);
+ }
}
}
}
@@ -188,13 +200,22 @@
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
String host = intent.getDataAuthority(index).getHost();
- // It's easy to misconfigure autoVerify intent filters, so to avoid
- // adding unintended hosts, check if the host is an HTTP domain.
- if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+ if (isValidHost(host)) {
domains.add(host);
}
}
}
}
}
+
+ /**
+ * It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts,
+ * check if the host is an HTTP domain. This applies for both legacy and modern versions of
+ * the API, which will strip invalid hosts from the legacy parsing result. This is done to
+ * improve the reliability of any legacy verifiers.
+ */
+ private boolean isValidHost(String host) {
+ mDomainMatcher.reset(host);
+ return mDomainMatcher.matches();
+ }
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 9389e63..a804065 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -30,7 +30,6 @@
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.Process;
import android.os.UserHandle;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -45,6 +44,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -168,22 +168,58 @@
return true;
}
- Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet());
- successfulDomains.removeAll(response.failedDomains);
+ AndroidPackage pkg = mConnection.getPackage(packageName);
+ if (pkg == null) {
+ return true;
+ }
+
+ ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains);
+ Map<String, Integer> hostToStateMap = info.getHostToStateMap();
+ Set<String> hostKeySet = hostToStateMap.keySet();
+ ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet);
+ successfulDomains.removeAll(failedDomains);
+
+ // v1 doesn't handle wildcard domains, so check them here for the verifier
+ int size = successfulDomains.size();
+ for (int index = size - 1; index >= 0; index--) {
+ String domain = successfulDomains.valueAt(index);
+ if (domain.startsWith("*.")) {
+ String nonWildcardDomain = domain.substring(2);
+ if (failedDomains.contains(nonWildcardDomain)) {
+ failedDomains.add(domain);
+ successfulDomains.removeAt(index);
+
+ // It's possible to declare a wildcard without declaring its
+ // non-wildcard equivalent, so if it wasn't originally declared,
+ // remove the transformed domain from the failed set. Otherwise the
+ // manager will not accept the failed set as it contains an undeclared
+ // domain.
+ if (!hostKeySet.contains(nonWildcardDomain)) {
+ failedDomains.remove(nonWildcardDomain);
+ }
+ }
+ }
+ }
int callingUid = response.callingUid;
- try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- successfulDomains, DomainVerificationState.STATE_SUCCESS);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException ignored) {
+ if (!successfulDomains.isEmpty()) {
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ successfulDomains, DomainVerificationState.STATE_SUCCESS);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
+ }
}
- try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- new ArraySet<>(response.failedDomains),
- DomainVerificationState.STATE_LEGACY_FAILURE);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException ignored) {
+
+ if (!failedDomains.isEmpty()) {
+ try {
+ mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
+ } catch (DomainVerificationManager.InvalidDomainSetException
+ | PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
+ }
}
return true;
@@ -235,7 +271,21 @@
// The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
// not the version of the verification agent on device.
ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
- return TextUtils.join(" ", domains);
+
+ // v1 doesn't handle wildcard domains, so transform them here to the root
+ StringBuilder builder = new StringBuilder();
+ int size = domains.size();
+ for (int index = 0; index < size; index++) {
+ if (index > 0) {
+ builder.append(" ");
+ }
+ String domain = domains.valueAt(index);
+ if (domain.startsWith("*.")) {
+ domain = domain.substring(2);
+ }
+ builder.append(domain);
+ }
+ return builder.toString();
}
private static class Response {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index ac358db..4e1065a 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -17,6 +17,7 @@
package com.android.server.policy;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -84,7 +85,8 @@
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
@VisibleForTesting
- static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+ static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
+ "DEFAULT");
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc81961..6a441f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -346,8 +346,21 @@
/** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
- /** Amount of time (in milliseconds) a toast window can be shown. */
- public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+ /**
+ * Extra time for additional SystemUI animations.
+ * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs,
+ * {@link DisplayPolicy} ensures that the window manager removes toast windows after
+ * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for
+ * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum
+ * of 3.5 seconds and the animations are finished before window manager removes the window.
+ */
+ public static final int TOAST_WINDOW_ANIM_BUFFER = 600;
+
+ /**
+ * Amount of time (in milliseconds) a toast window can be shown before it's automatically
+ * removed by window manager.
+ */
+ public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
/**
* Lock protecting internal state. Must not call out into window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..88fdc4a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
import android.view.Display;
import android.view.KeyEvent;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ea41980..b7285d5 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -73,6 +73,8 @@
private TimerTrigger mTimerTrigger;
@Nullable
private StatsPullAtomCallbackImpl mPullAtomCallback;
+ @Nullable
+ private PowerStatsInternal mPowerStatsInternal;
@VisibleForTesting
static class Injector {
@@ -125,8 +127,8 @@
}
StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
- IPowerStatsHALWrapper powerStatsHALWrapper) {
- return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+ PowerStatsInternal powerStatsInternal) {
+ return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
}
}
@@ -175,21 +177,14 @@
@Override
public void onStart() {
if (getPowerStatsHal().isInitialized()) {
- // Only create internal service if PowerStatsHal is available.
- publishLocalService(PowerStatsInternal.class, new LocalService());
+ mPowerStatsInternal = new LocalService();
+ publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
}
publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
}
private void onSystemServicesReady() {
- if (getPowerStatsHal().isInitialized()) {
- if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
-
- // Only start statsd pullers if initialization is successful.
- mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
- } else {
- Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
- }
+ mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
}
private void onBootCompleted() {
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index 7c6999a..bdabefb 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -24,26 +24,31 @@
import android.hardware.power.stats.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
import android.util.StatsEvent;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
* SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
*/
public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+ private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName();
private Context mContext;
- private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+ private PowerStatsInternal mPowerStatsInternal;
private Map<Integer, Channel> mChannels = new HashMap();
private Map<Integer, String> mEntityNames = new HashMap();
- private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+ private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+ private static final int STATS_PULL_TIMEOUT_MILLIS = 2000;
+ private static final boolean DEBUG = false;
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
@@ -57,21 +62,28 @@
}
}
- private void initPullOnDevicePowerMeasurement() {
- Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
- if (channels == null) {
- return;
+ private boolean initPullOnDevicePowerMeasurement() {
+ Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo();
+ if (channels == null || channels.length == 0) {
+ Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller");
+ return false;
}
for (int i = 0; i < channels.length; i++) {
final Channel channel = channels[i];
mChannels.put(channel.id, channel);
}
+
+ return true;
}
private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
- EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
- if (energyMeasurements == null) {
+ final EnergyMeasurement[] energyMeasurements;
+ try {
+ energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0])
+ .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to readEnergyMeterAsync", e);
return StatsManager.PULL_SKIP;
}
@@ -91,10 +103,11 @@
return StatsManager.PULL_SUCCESS;
}
- private void initSubsystemSleepState() {
- PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
- if (entities == null) {
- return;
+ private boolean initSubsystemSleepState() {
+ PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+ if (entities == null || entities.length == 0) {
+ Slog.e(TAG, "Failed to init SubsystemSleepState puller");
+ return false;
}
for (int i = 0; i < entities.length; i++) {
@@ -108,13 +121,20 @@
mEntityNames.put(entity.id, entity.name);
mStateNames.put(entity.id, states);
}
+
+ return true;
}
private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
- StateResidencyResult[] results = mPowerStatsHALWrapper.getStateResidency(new int[0]);
- if (results == null) {
+ final StateResidencyResult[] results;
+ try {
+ results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+ .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to getStateResidencyAsync", e);
return StatsManager.PULL_SKIP;
}
+
for (int i = 0; i < results.length; i++) {
final StateResidencyResult result = results[i];
for (int j = 0; j < result.stateResidencyData.length; j++) {
@@ -131,22 +151,33 @@
return StatsManager.PULL_SUCCESS;
}
- public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+ public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) {
+ if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
mContext = context;
- mPowerStatsHALWrapper = powerStatsHALWrapper;
- initPullOnDevicePowerMeasurement();
- initSubsystemSleepState();
+ mPowerStatsInternal = powerStatsInternal;
+
+ if (powerStatsInternal == null) {
+ Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+ return;
+ }
StatsManager manager = mContext.getSystemService(StatsManager.class);
- manager.setPullAtomCallback(
- FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
- null, // use default PullAtomMetadata values
- ConcurrentUtils.DIRECT_EXECUTOR,
- this);
- manager.setPullAtomCallback(
- FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
- null, // use default PullAtomMetadata values
- ConcurrentUtils.DIRECT_EXECUTOR,
- this);
+
+ if (initPullOnDevicePowerMeasurement()) {
+ manager.setPullAtomCallback(
+ FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ this);
+ }
+
+ if (initSubsystemSleepState()) {
+ manager.setPullAtomCallback(
+ FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ this);
+ }
}
}
diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS
index b94d988..31e3549 100644
--- a/services/core/java/com/android/server/role/OWNERS
+++ b/services/core/java/com/android/server/role/OWNERS
@@ -1,5 +1,4 @@
svetoslavganov@google.com
-moltmann@google.com
zhanghai@google.com
evanseverson@google.com
eugenesusla@google.com
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 539b413..0d43600 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1612,9 +1612,6 @@
// Aggregate times for the same uids.
SparseArray<long[]> aggregated = new SparseArray<>();
mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
- // For uids known to be aggregated from many entries allow mutating in place to avoid
- // many copies. Otherwise, copy before aggregating.
- boolean mutateInPlace = false;
if (UserHandle.isIsolated(uid)) {
// Skip individual isolated uids because they are recycled and quickly removed from
// the underlying data source.
@@ -1622,26 +1619,18 @@
} else if (UserHandle.isSharedAppGid(uid)) {
// All shared app gids are accounted together.
uid = LAST_SHARED_APPLICATION_GID;
- mutateInPlace = true;
} else {
// Everything else is accounted under their base uid.
uid = UserHandle.getAppId(uid);
}
long[] aggCpuFreqTimeMs = aggregated.get(uid);
- if (aggCpuFreqTimeMs != null) {
- if (!mutateInPlace) {
- aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
- aggregated.put(uid, aggCpuFreqTimeMs);
- }
- for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
- aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
- }
- } else {
- if (mutateInPlace) {
- cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
- }
- aggregated.put(uid, cpuFreqTimeMs);
+ if (aggCpuFreqTimeMs == null) {
+ aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+ aggregated.put(uid, aggCpuFreqTimeMs);
+ }
+ for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+ aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
}
});
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index bbbd19f..b210339 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 6a4c276..792f372 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 7cd4184..c4c620c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,7 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2503e81..37d13fb 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -125,10 +125,11 @@
public class VcnGatewayConnection extends StateMachine {
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
+
private static final int[] MERGED_CAPABILITIES =
new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
-
- private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
@@ -412,11 +413,11 @@
@NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
@NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
@NonNull private final Dependencies mDeps;
-
@NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
@NonNull private final IpSecManager mIpSecManager;
- @NonNull private final IpSecTunnelInterface mTunnelIface;
+
+ @Nullable private IpSecTunnelInterface mTunnelIface = null;
/** Running state of this VcnGatewayConnection. */
private boolean mIsRunning = true;
@@ -526,20 +527,6 @@
mUnderlyingNetworkTrackerCallback);
mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
- IpSecTunnelInterface iface;
- try {
- iface =
- mIpSecManager.createIpSecTunnelInterface(
- DUMMY_ADDR, DUMMY_ADDR, new Network(-1));
- } catch (IOException | ResourceUnavailableException e) {
- teardownAsynchronously();
- mTunnelIface = null;
-
- return;
- }
-
- mTunnelIface = iface;
-
addState(mDisconnectedState);
addState(mDisconnectingState);
addState(mConnectingState);
@@ -1117,6 +1104,18 @@
class ConnectedState extends ConnectedStateBase {
@Override
protected void enterState() throws Exception {
+ if (mTunnelIface == null) {
+ try {
+ // Requires a real Network object in order to be created; doing this any earlier
+ // means not having a real Network object, or picking an incorrect Network.
+ mTunnelIface =
+ mIpSecManager.createIpSecTunnelInterface(
+ DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
+ } catch (IOException | ResourceUnavailableException e) {
+ teardownAsynchronously();
+ }
+ }
+
// Successful connection, clear failed attempt counter
mFailedAttempts = 0;
}
@@ -1434,6 +1433,11 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
+ void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
+ mTunnelIface = tunnelIface;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
return mUnderlyingNetworkTrackerCallback;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bee66637..04dac7c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -92,13 +92,6 @@
@GuardedBy("mLock")
private boolean mForceStop;
- // TODO(b/159207608): Remove this constructor once VibratorService is removed
- public VibrationThread(Vibration vib, VibratorController vibrator,
- PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
- VibrationCallbacks callbacks) {
- this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
- }
-
public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
@@ -286,12 +279,6 @@
return filteredEffects;
}
- private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
- SparseArray<VibratorController> array = new SparseArray<>(1);
- array.put(controller.getVibratorInfo().getId(), controller);
- return array;
- }
-
/**
* Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
* startIndex} until the next time it's vibrating amplitude is zero.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3456e51..79f8229 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -772,11 +772,6 @@
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
}
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target));
- ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent,
- mRequest.activityInfo,
- mService.getProcessController(mRequest.caller),
- mSupervisor);
- newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle());
heavy.updateIntentForHeavyWeightActivity(newIntent);
newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
mRequest.activityInfo.packageName);
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6e8110e..d81181d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -58,6 +58,8 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
@@ -1886,6 +1888,7 @@
// Fill in some deprecated values.
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
+ rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
// Fill in organized child task info for the task created by organizer.
if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bd93e04..ceebe95 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,8 +847,6 @@
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
- // Send any pending task-info changes that were queued-up during a layout deferment
- mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
@@ -861,6 +859,8 @@
}
}
+ // Send any pending task-info changes that were queued-up during a layout deferment
+ mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
checkAppTransitionReady(surfacePlacer);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9bbbbe0..9c8a997 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -158,6 +158,7 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -293,6 +294,9 @@
private static final String ATTR_MIN_HEIGHT = "min_height";
private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
+ private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size";
+ private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
+ private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
// Set to false to disable the preview that is shown while a new activity
// is being started.
@@ -540,6 +544,10 @@
// NOTE: This value needs to be persisted with each task
private TaskDescription mTaskDescription;
+ // Information about the last snapshot that should be persisted with the task to allow SystemUI
+ // to layout without loading all the task snapshots
+ final PersistedTaskSnapshotData mLastTaskSnapshotData;
+
// If set to true, the task will report that it is not in the floating
// state regardless of it's root task affiliation. As the floating state drives
// production of content insets this can be used to preserve them across
@@ -613,8 +621,6 @@
SurfaceControl.Transaction mMainWindowSizeChangeTransaction;
Task mMainWindowSizeChangeTask;
- Rect mPreAnimationBounds = new Rect();
-
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -839,13 +845,13 @@
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
- TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
- int nextTaskId, int callingUid, String callingPackage,
- @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture,
- boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
- ActivityInfo info, IVoiceInteractionSession _voiceSession,
- IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer,
- IBinder _launchCookie, boolean _deferTaskAppear) {
+ TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
+ int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
+ String callingPackage, @Nullable String callingFeatureId, int resizeMode,
+ boolean supportsPictureInPicture, boolean _realActivitySuspended,
+ boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info,
+ IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+ boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear) {
super(atmService.mWindowManager);
mAtmService = atmService;
@@ -855,7 +861,12 @@
mUserId = _userId;
mResizeMode = resizeMode;
mSupportsPictureInPicture = supportsPictureInPicture;
- mTaskDescription = _lastTaskDescription;
+ mTaskDescription = _lastTaskDescription != null
+ ? _lastTaskDescription
+ : new TaskDescription();
+ mLastTaskSnapshotData = _lastSnapshotData != null
+ ? _lastSnapshotData
+ : new PersistedTaskSnapshotData();
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
setOrientation(SCREEN_ORIENTATION_UNSET);
mRemoteToken = new RemoteToken(this);
@@ -3899,6 +3910,7 @@
}
void onSnapshotChanged(TaskSnapshot snapshot) {
+ mLastTaskSnapshotData.set(snapshot);
mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(
mTaskId, snapshot);
}
@@ -4157,7 +4169,6 @@
void fillTaskInfo(TaskInfo info, boolean stripExtras) {
getNumRunningActivities(mReuseActivitiesReport);
info.userId = isLeafTask() ? mUserId : mCurrentUser;
- info.stackId = getRootTaskId();
info.taskId = mTaskId;
info.displayId = getDisplayId();
info.isRunning = getTopNonFinishingActivity() != null;
@@ -4707,6 +4718,19 @@
out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight);
out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION);
+ if (mLastTaskSnapshotData.taskSize != null) {
+ out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE,
+ mLastTaskSnapshotData.taskSize.flattenToString());
+ }
+ if (mLastTaskSnapshotData.contentInsets != null) {
+ out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS,
+ mLastTaskSnapshotData.contentInsets.flattenToString());
+ }
+ if (mLastTaskSnapshotData.bufferSize != null) {
+ out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE,
+ mLastTaskSnapshotData.bufferSize.flattenToString());
+ }
+
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
affinityIntent.saveToXml(out);
@@ -4774,6 +4798,7 @@
int taskId = INVALID_TASK_ID;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
+ PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
int taskAffiliation = INVALID_TASK_ID;
int prevTaskId = INVALID_TASK_ID;
int nextTaskId = INVALID_TASK_ID;
@@ -4883,6 +4908,15 @@
case ATTR_PERSIST_TASK_VERSION:
persistTaskVersion = Integer.parseInt(attrValue);
break;
+ case ATTR_LAST_SNAPSHOT_TASK_SIZE:
+ lastSnapshotData.taskSize = Point.unflattenFromString(attrValue);
+ break;
+ case ATTR_LAST_SNAPSHOT_CONTENT_INSETS:
+ lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue);
+ break;
+ case ATTR_LAST_SNAPSHOT_BUFFER_SIZE:
+ lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue);
+ break;
default:
if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
Slog.w(TAG, "Task: Unknown attribute=" + attrName);
@@ -4977,6 +5011,7 @@
.setLastTimeMoved(lastTimeOnTop)
.setNeverRelinquishIdentity(neverRelinquishIdentity)
.setLastTaskDescription(taskDescription)
+ .setLastSnapshotData(lastSnapshotData)
.setTaskAffiliation(taskAffiliation)
.setPrevAffiliateTaskId(prevTaskId)
.setNextAffiliateTaskId(nextTaskId)
@@ -7904,6 +7939,7 @@
private long mLastTimeMoved;
private boolean mNeverRelinquishIdentity;
private TaskDescription mLastTaskDescription;
+ private PersistedTaskSnapshotData mLastSnapshotData;
private int mTaskAffiliation;
private int mPrevAffiliateTaskId = INVALID_TASK_ID;
private int mNextAffiliateTaskId = INVALID_TASK_ID;
@@ -8104,6 +8140,11 @@
return this;
}
+ private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) {
+ mLastSnapshotData = lastSnapshotData;
+ return this;
+ }
+
private Builder setOrigActivity(ComponentName origActivity) {
mOrigActivity = origActivity;
return this;
@@ -8217,9 +8258,6 @@
mCallingPackage = mActivityInfo.packageName;
mResizeMode = mActivityInfo.resizeMode;
mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
- if (mLastTaskDescription == null) {
- mLastTaskDescription = new TaskDescription();
- }
final Task task = buildInner();
task.mHasBeenVisible = mHasBeenVisible;
@@ -8253,9 +8291,9 @@
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
- mNeverRelinquishIdentity, mLastTaskDescription, mTaskAffiliation,
- mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid, mCallingPackage,
- mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
+ mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
+ mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
+ mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,
mLaunchCookie, mDeferTaskAppear);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1c4b034..a7abf6a 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -149,7 +149,7 @@
"android.hardware.power@1.1",
"android.hardware.power-V1-cpp",
"android.hardware.power.stats@1.0",
- "android.hardware.power.stats-ndk_platform",
+ "android.hardware.power.stats-V1-ndk_platform",
"android.hardware.thermal@1.0",
"android.hardware.tv.input@1.0",
"android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4e47984..a6029cd 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -73,10 +73,6 @@
static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
- // TODO(b/167946816): remove this once VibratorService is removed.
- if (vibratorId < 0) {
- return std::move(std::make_unique<vibrator::HalController>());
- }
vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
if (manager == nullptr) {
return nullptr;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bd10992..b063e67 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -62,6 +62,7 @@
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.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
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;
@@ -92,7 +93,6 @@
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -157,9 +157,9 @@
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicySafetyChecker;
import android.app.admin.DeviceStateCache;
@@ -1101,7 +1101,7 @@
*/
private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
int reason = getUnsafeOperationReason(operation);
- if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+ if (reason == OPERATION_SAFETY_REASON_NONE) return;
if (mSafetyChecker == null) {
// Happens on CTS after it's set just once (by OneTimeSafetyChecker)
@@ -1114,23 +1114,28 @@
/**
* Returns whether it's safe to execute the given {@code operation}, and why.
*/
- @UnsafeOperationReason
+ @OperationSafetyReason
int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
- return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+ return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE
: mSafetyChecker.getUnsafeOperationReason(operation);
}
@Override
public void setNextOperationSafety(@DevicePolicyOperation int operation,
- @UnsafeOperationReason int reason) {
+ @OperationSafetyReason int reason) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
DevicePolicyManager.operationToString(operation),
- DevicePolicyManager.unsafeOperationReasonToString(reason)));
+ DevicePolicyManager.operationSafetyReasonToString(reason)));
mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
}
+ @Override
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason);
+ }
+
// Used by DevicePolicyManagerServiceShellCommand
List<OwnerDto> listAllOwners() {
Preconditions.checkCallAuthorization(
@@ -7522,19 +7527,23 @@
sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
}
- private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
- sendActiveAdminCommand(action, extras, userHandle,
- mOwners.getProfileOwnerComponent(userHandle));
+ private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) {
+ sendActiveAdminCommand(action, extras, userId,
+ mOwners.getProfileOwnerComponent(userId));
}
private void sendActiveAdminCommand(String action, Bundle extras,
- int userHandle, ComponentName receiverComponent) {
+ @UserIdInt int userId, ComponentName receiverComponent) {
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, "sending intent " + action + " to "
+ + receiverComponent.flattenToShortString() + " on user " + userId);
+ }
final Intent intent = new Intent(action);
intent.setComponent(receiverComponent);
if (extras != null) {
intent.putExtras(extras);
}
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle));
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
private void sendOwnerChangedBroadcast(String broadcast, int userId) {
@@ -12224,6 +12233,32 @@
packageName, findInteractAcrossProfilesResetMode(packageName), userId);
}
+ @Override
+ public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
+ boolean isSafe) {
+ // TODO(b/178494483): use EventLog instead
+ // TODO(b/178494483): log metrics?
+ if (VERBOSE_LOG) {
+ Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
+ DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+ }
+
+ Preconditions.checkArgument(mSafetyChecker == checker,
+ "invalid checker: should be %s, was %s", mSafetyChecker, checker);
+
+ Bundle extras = new Bundle();
+ extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
+ extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
+
+ // TODO(b/178494483): add CTS test
+ sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras);
+ for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+ sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+ extras, profileOwnerId);
+ }
+ }
+
private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
return getDefaultCrossProfilePackages().contains(packageName)
? AppOpsManager.MODE_ALLOWED
@@ -16328,8 +16363,10 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+ final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ ? UserHandle.USER_SYSTEM : caller.getUserId();
if (!removeNonRequiredAppsForManagedDevice(
- caller.getUserId(),
+ deviceOwnerUserId,
provisioningParams.isLeaveAllSystemAppsEnabled(),
deviceAdmin)) {
throw new ServiceSpecificException(
@@ -16337,15 +16374,16 @@
"PackageManager failed to remove non required apps.");
}
+
if (!setActiveAdminAndDeviceOwner(
- caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) {
+ deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
throw new ServiceSpecificException(
PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
"Failed to set device owner.");
}
disallowAddUser();
- setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+ setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
provisioningParams.canDeviceOwnerGrantSensorsPermissions());
} catch (Exception e) {
DevicePolicyEventLogger
@@ -16388,30 +16426,42 @@
}
private boolean removeNonRequiredAppsForManagedDevice(
- int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
+ @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
Set<String> packagesToDelete = leaveAllSystemAppsEnabled
? Collections.emptySet()
: mOverlayPackagesProvider.getNonRequiredApps(
admin, userId, ACTION_PROVISION_MANAGED_DEVICE);
+
+ removeNonInstalledPackages(packagesToDelete, userId);
if (packagesToDelete.isEmpty()) {
+ Slog.i(LOG_TAG, "No packages to delete on user " + userId);
return true;
}
+
NonRequiredPackageDeleteObserver packageDeleteObserver =
new NonRequiredPackageDeleteObserver(packagesToDelete.size());
for (String packageName : packagesToDelete) {
- if (isPackageInstalledForUser(packageName, userId)) {
- Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
- mContext.getPackageManager().deletePackageAsUser(
- packageName,
- packageDeleteObserver,
- PackageManager.DELETE_SYSTEM_APP,
- userId);
- }
+ Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
+ mContext.getPackageManager().deletePackageAsUser(
+ packageName,
+ packageDeleteObserver,
+ PackageManager.DELETE_SYSTEM_APP,
+ userId);
}
Slog.i(LOG_TAG, "Waiting for non required apps to be deleted");
return packageDeleteObserver.awaitPackagesDeletion();
}
+ private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) {
+ final Set<String> toBeRemoved = new HashSet<>();
+ for (String packageName : packages) {
+ if (!isPackageInstalledForUser(packageName, userId)) {
+ toBeRemoved.add(packageName);
+ }
+ }
+ packages.removeAll(toBeRemoved);
+ }
+
private void disallowAddUser() {
if (mInjector.userManagerIsHeadlessSystemUserMode()) {
Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 222c987..5484a14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -27,6 +27,7 @@
final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe";
+ private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason";
private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe";
private static final String CMD_LIST_OWNERS = "list-owners";
@@ -53,6 +54,8 @@
switch (cmd) {
case CMD_IS_SAFE_OPERATION:
return runIsSafeOperation(pw);
+ case CMD_IS_SAFE_OPERATION_BY_REASON:
+ return runIsSafeOperationByReason(pw);
case CMD_SET_SAFE_OPERATION:
return runSetSafeOperation(pw);
case CMD_LIST_OWNERS:
@@ -73,12 +76,13 @@
return -1;
}
-
private void showHelp(PrintWriter pw) {
pw.printf(" help\n");
pw.printf(" Prints this help text.\n\n");
pw.printf(" %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
pw.printf(" Checks if the give operation is safe \n\n");
+ pw.printf(" %s <REASON_ID>\n", CMD_IS_SAFE_OPERATION_BY_REASON);
+ pw.printf(" Checks if the operations are safe for the given reason\n\n");
pw.printf(" %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
pw.printf(" Emulates the result of the next call to check if the given operation is safe"
+ " \n\n");
@@ -89,10 +93,19 @@
private int runIsSafeOperation(PrintWriter pw) {
int operation = Integer.parseInt(getNextArgRequired());
int reason = mService.getUnsafeOperationReason(operation);
- boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+ boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
pw.printf("Operation %s is %b. Reason: %s\n",
DevicePolicyManager.operationToString(operation), safe,
- DevicePolicyManager.unsafeOperationReasonToString(reason));
+ DevicePolicyManager.operationSafetyReasonToString(reason));
+ return 0;
+ }
+
+ private int runIsSafeOperationByReason(PrintWriter pw) {
+ int reason = Integer.parseInt(getNextArgRequired());
+ boolean safe = mService.isSafeOperation(reason);
+ pw.printf("Operations affected by %s are %s\n",
+ DevicePolicyManager.operationSafetyReasonToString(reason),
+ (safe ? "SAFE" : "UNSAFE"));
return 0;
}
@@ -102,7 +115,7 @@
mService.setNextOperationSafety(operation, reason);
pw.printf("Next call to check operation %s will return %s\n",
DevicePolicyManager.operationToString(operation),
- DevicePolicyManager.unsafeOperationReasonToString(reason));
+ DevicePolicyManager.operationSafetyReasonToString(reason));
return 0;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index 883f95d..7de1bd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,16 +15,18 @@
*/
package com.android.server.devicepolicy;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
import static android.app.admin.DevicePolicyManager.operationToString;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicySafetyChecker;
import android.util.Slog;
import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
import java.util.Objects;
@@ -43,10 +45,10 @@
private final DevicePolicyManagerService mService;
private final DevicePolicySafetyChecker mRealSafetyChecker;
private final @DevicePolicyOperation int mOperation;
- private final @UnsafeOperationReason int mReason;
+ private final @OperationSafetyReason int mReason;
OneTimeSafetyChecker(DevicePolicyManagerService service,
- @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
+ @DevicePolicyOperation int operation, @OperationSafetyReason int reason) {
mService = Objects.requireNonNull(service);
mOperation = operation;
mReason = reason;
@@ -55,24 +57,42 @@
}
@Override
- @UnsafeOperationReason
+ @OperationSafetyReason
public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
String name = operationToString(operation);
- int reason = UNSAFE_OPERATION_REASON_NONE;
+ Slog.i(TAG, "getUnsafeOperationReason(" + name + ")");
+ int reason = OPERATION_SAFETY_REASON_NONE;
if (operation == mOperation) {
reason = mReason;
} else {
Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
+ ", should be " + operationToString(mOperation));
}
- Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
- + unsafeOperationReasonToString(reason)
+ String reasonName = operationSafetyReasonToString(reason);
+ DevicePolicyManagerInternal dpmi = LocalServices
+ .getService(DevicePolicyManagerInternal.class);
+
+ Slog.i(TAG, "notifying " + reasonName + " is active");
+ dpmi.notifyUnsafeOperationStateChanged(this, reason, true);
+
+ Slog.i(TAG, "notifying " + reasonName + " is inactive");
+ dpmi.notifyUnsafeOperationStateChanged(this, reason, false);
+
+ Slog.i(TAG, "returning " + reasonName
+ " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
return reason;
}
@Override
+ public boolean isSafeOperation(@OperationSafetyReason int reason) {
+ boolean safe = mReason != reason;
+ Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe);
+
+ return safe;
+ }
+
+ @Override
public void onFactoryReset(IResultReceiver callback) {
throw new UnsupportedOperationException();
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 56cb3d1..886c1e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -69,6 +69,14 @@
static constexpr auto progressUpdateInterval = 1000ms;
static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
+
+ // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
+ static constexpr auto healthyDataLoaderUptime = 10min;
+ // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+ static constexpr auto minBindDelay = 10s;
+ static constexpr auto maxBindDelay = 10000s;
+ static constexpr auto bindDelayMultiplier = 10;
+ static constexpr auto bindDelayJitterDivider = 10;
};
static const Constants& constants() {
@@ -386,6 +394,28 @@
dprintf(fd, "}\n");
}
+bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+ if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+ return true;
+ }
+
+ // Check all permanent binds.
+ for (auto&& [_, bindPoint] : ifs.bindPoints) {
+ if (bindPoint.kind != BindKind::Permanent) {
+ continue;
+ }
+ const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir,
+ /*stopOnFirstIncomplete=*/true);
+ if (!progress.isError() && !progress.fullyLoaded()) {
+ LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir
+ << "], partial progress: " << progress.getProgress() * 100 << "%";
+ return true;
+ }
+ }
+
+ return false;
+}
+
void IncrementalService::onSystemReady() {
if (mSystemReady.exchange(true)) {
return;
@@ -396,8 +426,11 @@
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id &&
- ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
+ if (ifs->mountId != id) {
+ continue;
+ }
+
+ if (needStartDataLoaderLocked(*ifs)) {
mounts.push_back(ifs);
}
}
@@ -1539,6 +1572,11 @@
return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}
+template <class Duration>
+static constexpr auto castToMs(Duration d) {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(d);
+}
+
// Extract lib files from zip, create new files in incfs and write data to them
// Lib files should be placed next to the APK file in the following matter:
// Example:
@@ -2134,9 +2172,43 @@
<< status << " (current " << mCurrentStatus << ")";
}
+Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+ std::unique_lock lock(mMutex);
+ const auto previousBindTs = mPreviousBindTs;
+ const auto now = Clock::now();
+ mPreviousBindTs = now;
+
+ const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
+ if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
+ nonCrashingInterval > Constants::healthyDataLoaderUptime) {
+ mPreviousBindDelay = 0ms;
+ return mPreviousBindDelay;
+ }
+
+ constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
+ constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay);
+
+ const auto bindDelayMs =
+ std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs),
+ maxBindDelayMs)
+ .count();
+ const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
+ const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+ mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
+
+ return mPreviousBindDelay;
+}
+
bool IncrementalService::DataLoaderStub::bind() {
+ const auto bindDelay = updateBindDelay();
+ if (bindDelay > 1s) {
+ LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
+ << bindDelay.count() / 1000 << "s";
+ }
+
bool result = false;
- auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
+ auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
+ this, &result);
if (!status.isOk() || !result) {
LOG(ERROR) << "Failed to bind a data loader for mount " << id();
return false;
@@ -2249,7 +2321,8 @@
listener = mStatusListener;
- if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
+ if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+ mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
// For unavailable, unbind from DataLoader to ensure proper re-commit.
setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
}
@@ -2544,6 +2617,9 @@
dprintf(fd, " blockIndex: %d\n", pendingRead.block);
dprintf(fd, " bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs);
}
+ dprintf(fd, " bind: %llds ago (delay: %llds)\n",
+ (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000),
+ (long long)(mPreviousBindDelay.count() / 1000));
dprintf(fd, " }\n");
const auto& params = mParams;
dprintf(fd, " dataLoaderParams: {\n");
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5d53bac..459ed32 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -245,7 +245,6 @@
void setTargetStatusLocked(int status);
bool fsmStep();
- bool fsmStep(int currentStatus, int targetStatus);
void onHealthStatus(StorageHealthListener healthListener, int healthStatus);
void updateHealthStatus(bool baseline = false);
@@ -259,6 +258,8 @@
BootClockTsUs getOldestPendingReadTs();
+ Milliseconds updateBindDelay();
+
void registerForPendingReads();
void unregisterFromPendingReads();
@@ -276,6 +277,9 @@
int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
TimePoint mTargetStatusTs = {};
+ TimePoint mPreviousBindTs = {};
+ Milliseconds mPreviousBindDelay = {};
+
std::string mHealthPath;
incfs::UniqueControl mHealthControl;
struct {
@@ -370,6 +374,8 @@
void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
std::string&& source, std::string&& target, BindKind kind);
+ bool needStartDataLoaderLocked(IncFsMount& ifs);
+
DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
content::pm::DataLoaderParamsParcel&& params,
const DataLoaderStatusListener* statusListener = nullptr,
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 25d3f77..659d650 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -70,9 +70,10 @@
~RealDataLoaderManager() = default;
binder::Status bindToDataLoader(MountId mountId,
const content::pm::DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<content::pm::IDataLoaderStatusListener>& listener,
bool* _aidl_return) const final {
- return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return);
+ return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
}
binder::Status getDataLoader(MountId mountId,
sp<content::pm::IDataLoader>* _aidl_return) const final {
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 71fd3ac..d60035a 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -63,7 +63,7 @@
public:
virtual ~DataLoaderManagerWrapper() = default;
virtual binder::Status bindToDataLoader(
- MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+ MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs,
const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
virtual binder::Status getDataLoader(MountId mountId,
sp<content::pm::IDataLoader>* result) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8713f9d..ab491ef 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -208,8 +208,9 @@
EXPECT_TRUE(mDataLoaderHolder != nullptr);
}
- MOCK_CONST_METHOD4(bindToDataLoader,
+ MOCK_CONST_METHOD5(bindToDataLoader,
binder::Status(int32_t mountId, const DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return));
MOCK_CONST_METHOD2(getDataLoader,
@@ -217,11 +218,11 @@
MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId));
void bindToDataLoaderSuccess() {
- ON_CALL(*this, bindToDataLoader(_, _, _, _))
+ ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk));
}
void bindToDataLoaderFails() {
- ON_CALL(*this, bindToDataLoader(_, _, _, _))
+ ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Return(
(binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
}
@@ -234,6 +235,7 @@
.WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk));
}
binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
+ int bindDelayMs,
const sp<IDataLoaderStatusListener>& listener,
bool* _aidl_return) {
mId = mountId;
@@ -245,6 +247,40 @@
}
return binder::Status::ok();
}
+ binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+ binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+ << bindDelayMs;
+ return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+ }
+
binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
*_aidl_return = mDataLoader;
return binder::Status::ok();
@@ -261,6 +297,9 @@
void setDataLoaderStatusUnavailable() {
mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
}
+ void setDataLoaderStatusUnrecoverable() {
+ mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
+ }
binder::Status unbindFromDataLoaderOk(int32_t id) {
if (mDataLoader) {
if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -676,7 +715,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
mVold->mountIncFsFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -686,7 +725,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
mVold->mountIncFsInvalidControlParcel();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
TemporaryDir tempDir;
int storageId =
@@ -698,7 +737,7 @@
TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
mVold->mountIncFsSuccess();
mIncFs->makeFileFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
@@ -712,7 +751,7 @@
mVold->mountIncFsSuccess();
mIncFs->makeFileSuccess();
mVold->bindMountFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
@@ -727,7 +766,7 @@
mIncFs->makeFileSuccess();
mVold->bindMountSuccess();
mDataLoaderManager->bindToDataLoaderFails();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -755,11 +794,11 @@
mIncrementalService->deleteStorage(storageId);
}
-TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
- EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
- EXPECT_CALL(*mDataLoader, start(_)).Times(2);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(6);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
@@ -768,13 +807,38 @@
IncrementalService::CreateOptions::CreateNew);
ASSERT_GE(storageId, 0);
mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
// Simulated crash/other connection breakage.
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
mDataLoaderManager->setDataLoaderStatusDestroyed();
}
TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -793,7 +857,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -811,7 +875,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -827,12 +891,30 @@
mDataLoaderManager->setDataLoaderStatusUnavailable();
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) {
+ mDataLoader->initializeCreateOkNoStatus();
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+ mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+}
+
TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
mIncFs->waitForPendingReadsSuccess();
mIncFs->openMountSuccess();
mDataLoader->initializeCreateOkNoStatus();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -856,7 +938,7 @@
TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
mIncFs->openMountSuccess();
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -1406,7 +1488,7 @@
}
TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
- EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98c3b99..a0e5c5d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -525,8 +525,7 @@
String filename = "/data/system/heapdump/fdtrack-" + date + ".hprof";
Debug.dumpHprofData(filename);
} catch (IOException ex) {
- Slog.e("System", "Failed to dump fdtrack hprof");
- ex.printStackTrace();
+ Slog.e("System", "Failed to dump fdtrack hprof", ex);
}
}
@@ -1292,7 +1291,6 @@
t.traceBegin("startOtherServices");
final Context context = mSystemContext;
- VibratorService vibrator = null;
DynamicSystemService dynamicSystem = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
@@ -1418,11 +1416,6 @@
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
t.traceEnd();
- t.traceBegin("StartVibratorService");
- vibrator = new VibratorService(context);
- ServiceManager.addService("vibrator", vibrator);
- t.traceEnd();
-
t.traceBegin("StartDynamicSystemService");
dynamicSystem = new DynamicSystemService(context);
ServiceManager.addService("dynamic_system", dynamicSystem);
@@ -2491,14 +2484,6 @@
// It is now time to start up the app processes...
- t.traceBegin("MakeVibratorServiceReady");
- try {
- vibrator.systemReady();
- } catch (Throwable e) {
- reportWtf("making Vibrator Service ready", e);
- }
- t.traceEnd();
-
t.traceBegin("MakeLockSettingsServiceReady");
if (lockSettings != null) {
try {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
new file mode 100644
index 0000000..195cc01
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -0,0 +1,725 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.apex.ApexSessionInfo;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
+import android.os.SystemProperties;
+import android.os.storage.IStorageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+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 static org.testng.Assert.assertThrows;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class StagingManagerTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Mock private Context mContext;
+ @Mock private IStorageManager mStorageManager;
+ @Mock private ApexManager mApexManager;
+
+ private File mTmpDir;
+ private StagingManager mStagingManager;
+
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
+
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(PackageHelper.class)
+ .startMocking();
+
+ when(mStorageManager.supportsCheckpoint()).thenReturn(true);
+ when(mStorageManager.needsCheckpoint()).thenReturn(true);
+ when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+
+ when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
+ when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
+
+ mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
+ mStagingManager = new StagingManager(mContext, null, mApexManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
+ }
+
+ /**
+ * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
+ * check.
+ */
+ @Test
+ public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
+ throws Exception {
+ // Create 2 sessions with overlapping packages
+ StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
+ StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
+
+ mStagingManager.createSession(session1);
+ mStagingManager.createSession(session2);
+ // Session1 should not fail in spite of the overlapping packages
+ mStagingManager.checkNonOverlappingWithStagedSessions(session1);
+ // Session2 should fail due to overlapping packages
+ assertThrows(PackageManagerException.class,
+ () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
+ }
+
+ @Test
+ public void restoreSessions_nonParentSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+ session.setParentSessionId(1543);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_terminalSession_throwsIAE() throws Exception {
+ FakeStagedSession session = new FakeStagedSession(239);
+ session.setCommitted(true);
+ session.setSessionApplied();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+ }
+
+ @Test
+ public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception {
+ FakeStagedSession session1 = new FakeStagedSession(37);
+ session1.setCommitted(true);
+ FakeStagedSession session2 = new FakeStagedSession(57);
+ session2.setCommitted(true);
+
+ mStagingManager.restoreSessions(Arrays.asList(session1, session2), true);
+
+ assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+
+ assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+ }
+
+ @Test
+ public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE()
+ throws Exception {
+ FakeStagedSession session1 = new FakeStagedSession(37);
+ session1.setCommitted(true);
+ FakeStagedSession session2 = new FakeStagedSession(57);
+ session2.setCommitted(true);
+
+ when(mStorageManager.supportsCheckpoint()).thenReturn(false);
+
+ assertThrows(IllegalStateException.class,
+ () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false));
+ }
+
+ @Test
+ public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception {
+ FakeStagedSession destroyedApkSession = new FakeStagedSession(23);
+ destroyedApkSession.setCommitted(true);
+ destroyedApkSession.setDestroyed(true);
+
+ FakeStagedSession destroyedApexSession = new FakeStagedSession(37);
+ destroyedApexSession.setCommitted(true);
+ destroyedApexSession.setDestroyed(true);
+ destroyedApexSession.setIsApex(true);
+
+ FakeStagedSession nonReadyApkSession = new FakeStagedSession(57);
+ nonReadyApkSession.setCommitted(true);
+
+ FakeStagedSession nonReadyApexSession = new FakeStagedSession(73);
+ nonReadyApexSession.setCommitted(true);
+ nonReadyApexSession.setIsApex(true);
+
+ FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101);
+ destroyedNonReadySession.setCommitted(true);
+ destroyedNonReadySession.setDestroyed(true);
+
+ FakeStagedSession regularApkSession = new FakeStagedSession(239);
+ regularApkSession.setCommitted(true);
+ regularApkSession.setSessionReady();
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(destroyedApkSession);
+ sessions.add(destroyedApexSession);
+ sessions.add(nonReadyApkSession);
+ sessions.add(nonReadyApexSession);
+ sessions.add(destroyedNonReadySession);
+ sessions.add(regularApkSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ assertThat(sessions).containsExactly(regularApkSession);
+ assertThat(destroyedApkSession.isDestroyed()).isTrue();
+ assertThat(destroyedApexSession.isDestroyed()).isTrue();
+ assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
+
+ mStagingManager.onBootCompletedBroadcastReceived();
+ assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
+ assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
+ }
+
+ @Test
+ public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ when(mApexManager.getSessions()).thenReturn(new SparseArray<>());
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+ + "staged session supposed to be activated");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+ apexSession1.setCommitted(true);
+ apexSession1.setIsApex(true);
+ apexSession1.setSessionReady();
+
+ FakeStagedSession apexSession2 = new FakeStagedSession(101);
+ apexSession2.setCommitted(true);
+ apexSession2.setIsApex(true);
+ apexSession2.setSessionReady();
+
+ FakeStagedSession apexSession3 = new FakeStagedSession(57);
+ apexSession3.setCommitted(true);
+ apexSession3.setIsApex(true);
+ apexSession3.setSessionReady();
+
+ ApexSessionInfo activationFailed = new ApexSessionInfo();
+ activationFailed.sessionId = 1543;
+ activationFailed.isActivationFailed = true;
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 101;
+ staged.isStaged = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, activationFailed);
+ apexdSessions.put(101, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession1);
+ sessions.add(apexSession2);
+ sessions.add(apexSession3);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession1.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat "
+ + "messages from apexd for more information.");
+
+ assertThat(apexSession2.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't "
+ + "activate nor fail. Marking it as failed anyway.");
+
+ assertThat(apexSession3.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+ + "staged session supposed to be activated");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 1543;
+ staged.isStaged = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't "
+ + "activate nor fail. Marking it as failed anyway.");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ @Test
+ public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+ apexSession1.setCommitted(true);
+ apexSession1.setIsApex(true);
+ apexSession1.setSessionReady();
+
+ FakeStagedSession apexSession2 = new FakeStagedSession(101);
+ apexSession2.setCommitted(true);
+ apexSession2.setIsApex(true);
+ apexSession2.setSessionReady();
+
+ FakeStagedSession apexSession3 = new FakeStagedSession(57);
+ apexSession3.setCommitted(true);
+ apexSession3.setIsApex(true);
+ apexSession3.setSessionReady();
+
+ FakeStagedSession apexSession4 = new FakeStagedSession(37);
+ apexSession4.setCommitted(true);
+ apexSession4.setIsApex(true);
+ apexSession4.setSessionReady();
+
+ ApexSessionInfo activationFailed = new ApexSessionInfo();
+ activationFailed.sessionId = 1543;
+ activationFailed.isActivationFailed = true;
+
+ ApexSessionInfo activated = new ApexSessionInfo();
+ activated.sessionId = 101;
+ activated.isActivated = true;
+
+ ApexSessionInfo staged = new ApexSessionInfo();
+ staged.sessionId = 57;
+ staged.isActivationFailed = true;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, activationFailed);
+ apexdSessions.put(101, activated);
+ apexdSessions.put(57, staged);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession1);
+ sessions.add(apexSession2);
+ sessions.add(apexSession3);
+ sessions.add(apexSession4);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint was aborted.
+ verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false));
+ }
+
+ @Test
+ public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception {
+ FakeStagedSession apkSession = new FakeStagedSession(239);
+ apkSession.setCommitted(true);
+ apkSession.setSessionReady();
+
+ FakeStagedSession apexSession = new FakeStagedSession(1543);
+ apexSession.setCommitted(true);
+ apexSession.setIsApex(true);
+ apexSession.setSessionReady();
+
+ ApexSessionInfo impossible = new ApexSessionInfo();
+ impossible.sessionId = 1543;
+
+ SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+ apexdSessions.put(1543, impossible);
+ when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+ List<StagingManager.StagedSession> sessions = new ArrayList<>();
+ sessions.add(apkSession);
+ sessions.add(apexSession);
+
+ mStagingManager.restoreSessions(sessions, false);
+
+ // Validate checkpoint wasn't aborted.
+ verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+ assertThat(apexSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state");
+
+ assertThat(apkSession.getErrorCode())
+ .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+ assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+ }
+
+ private StagingManager.StagedSession createSession(int sessionId, String packageName,
+ long committedMillis) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.isStaged = true;
+
+ InstallSource installSource = InstallSource.create("testInstallInitiator",
+ "testInstallOriginator", "testInstaller", "testAttributionTag");
+
+ PackageInstallerSession session = new PackageInstallerSession(
+ /* callback */ null,
+ /* context */ null,
+ /* pm */ null,
+ /* sessionProvider */ null,
+ /* looper */ BackgroundThread.getHandler().getLooper(),
+ /* stagingManager */ null,
+ /* sessionId */ sessionId,
+ /* userId */ 456,
+ /* installerUid */ -1,
+ /* installSource */ installSource,
+ /* sessionParams */ params,
+ /* createdMillis */ 0L,
+ /* committedMillis */ committedMillis,
+ /* stageDir */ mTmpDir,
+ /* stageCid */ null,
+ /* files */ null,
+ /* checksums */ null,
+ /* prepared */ true,
+ /* committed */ true,
+ /* destroyed */ false,
+ /* sealed */ false, // Setting to true would trigger some PM logic.
+ /* childSessionIds */ null,
+ /* parentSessionId */ -1,
+ /* isReady */ false,
+ /* isFailed */ false,
+ /* isApplied */false,
+ /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
+ /* stagedSessionErrorMessage */ "no error");
+
+ StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
+ doReturn(packageName).when(stagedSession).getPackageName();
+ doAnswer(invocation -> {
+ Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
+ return filter.test(stagedSession);
+ }).when(stagedSession).sessionContains(any());
+ return stagedSession;
+ }
+
+ private static final class FakeStagedSession implements StagingManager.StagedSession {
+ private final int mSessionId;
+ private boolean mIsApex = false;
+ private boolean mIsCommitted = false;
+ private boolean mIsReady = false;
+ private boolean mIsApplied = false;
+ private boolean mIsFailed = false;
+ private @StagedSessionErrorCode int mErrorCode = -1;
+ private String mErrorMessage;
+ private boolean mIsDestroyed = false;
+ private int mParentSessionId = -1;
+ private String mPackageName;
+ private boolean mIsAbandonded = false;
+ private boolean mPreRebootVerificationStarted = false;
+ private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
+
+ private FakeStagedSession(int sessionId) {
+ mSessionId = sessionId;
+ }
+
+ private void setParentSessionId(int parentSessionId) {
+ mParentSessionId = parentSessionId;
+ }
+
+ private void setCommitted(boolean isCommitted) {
+ mIsCommitted = isCommitted;
+ }
+
+ private void setIsApex(boolean isApex) {
+ mIsApex = isApex;
+ }
+
+ private void setDestroyed(boolean isDestroyed) {
+ mIsDestroyed = isDestroyed;
+ }
+
+ private void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+
+ private boolean isAbandonded() {
+ return mIsAbandonded;
+ }
+
+ private boolean hasPreRebootVerificationStarted() {
+ return mPreRebootVerificationStarted;
+ }
+
+ private FakeStagedSession addChildSession(FakeStagedSession session) {
+ mChildSessions.add(session);
+ session.setParentSessionId(sessionId());
+ return this;
+ }
+
+ private @StagedSessionErrorCode int getErrorCode() {
+ return mErrorCode;
+ }
+
+ private String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ @Override
+ public boolean isMultiPackage() {
+ return !mChildSessions.isEmpty();
+ }
+
+ @Override
+ public boolean isApexSession() {
+ return mIsApex;
+ }
+
+ @Override
+ public boolean isCommitted() {
+ return mIsCommitted;
+ }
+
+ @Override
+ public boolean isInTerminalState() {
+ return isSessionApplied() || isSessionFailed();
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mIsDestroyed;
+ }
+
+ @Override
+ public boolean isSessionReady() {
+ return mIsReady;
+ }
+
+ @Override
+ public boolean isSessionApplied() {
+ return mIsApplied;
+ }
+
+ @Override
+ public boolean isSessionFailed() {
+ return mIsFailed;
+ }
+
+ @Override
+ public List<StagingManager.StagedSession> getChildSessions() {
+ return mChildSessions;
+ }
+
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public int getParentSessionId() {
+ return mParentSessionId;
+ }
+
+ @Override
+ public int sessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public PackageInstaller.SessionParams sessionParams() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) {
+ return filter.test(this);
+ }
+
+ @Override
+ public boolean containsApkSession() {
+ Preconditions.checkState(!hasParentSessionId(), "Child session");
+ if (!isMultiPackage()) {
+ return !isApexSession();
+ }
+ for (StagingManager.StagedSession session : mChildSessions) {
+ if (!session.isApexSession()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean containsApexSession() {
+ Preconditions.checkState(!hasParentSessionId(), "Child session");
+ if (!isMultiPackage()) {
+ return isApexSession();
+ }
+ for (StagingManager.StagedSession session : mChildSessions) {
+ if (session.isApexSession()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setSessionReady() {
+ mIsReady = true;
+ }
+
+ @Override
+ public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
+ Preconditions.checkState(!mIsApplied, "Already marked as applied");
+ mIsFailed = true;
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ }
+
+ @Override
+ public void setSessionApplied() {
+ Preconditions.checkState(!mIsFailed, "Already marked as failed");
+ mIsApplied = true;
+ }
+
+ @Override
+ public void installSession(IntentSender statusReceiver) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasParentSessionId() {
+ return mParentSessionId != -1;
+ }
+
+ @Override
+ public long getCommittedMillis() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void abandon() {
+ mIsAbandonded = true;
+ }
+
+ @Override
+ public boolean notifyStartPreRebootVerification() {
+ mPreRebootVerificationStarted = true;
+ // TODO(ioffe): change to true when tests for pre-reboot verification are added.
+ return false;
+ }
+
+ @Override
+ public void notifyEndPreRebootVerification() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void verifySession() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index f0d7006..da3d1d6 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -198,6 +198,10 @@
return mVibratorProviders.get(vibratorId)
.newVibratorController(vibratorId, listener);
}
+
+ @Override
+ void addService(String name, IBinder service) {
+ }
});
service.systemReady();
return service;
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
deleted file mode 100644
index 633957a..0000000
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ /dev/null
@@ -1,757 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.vibrator.IVibrator;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibratorService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorServiceTest
- */
-@Presubmit
-public class VibratorServiceTest {
-
- private static final int TEST_TIMEOUT_MILLIS = 1_000;
- private static final int UID = Process.ROOT_UID;
- private static final int VIBRATOR_ID = 1;
- private static final String PACKAGE_NAME = "package";
- private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
- private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
- .setBatterySaverEnabled(true).build();
- private static final VibrationAttributes ALARM_ATTRS =
- new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
- private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_TOUCH).build();
- private static final VibrationAttributes NOTIFICATION_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_NOTIFICATION).build();
- private static final VibrationAttributes RINGTONE_ATTRS =
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_RINGTONE).build();
-
- @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
- @Mock private PackageManagerInternal mPackageManagerInternalMock;
- @Mock private PowerManagerInternal mPowerManagerInternalMock;
- @Mock private AppOpsManager mAppOpsManagerMock;
- @Mock private IVibratorStateListener mVibratorStateListenerMock;
- @Mock private IInputManager mIInputManagerMock;
- @Mock private IBinder mVibratorStateListenerBinderMock;
-
- private TestLooper mTestLooper;
- private ContextWrapper mContextSpy;
- private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
- private FakeVibrator mFakeVibrator;
- private FakeVibratorControllerProvider mVibratorProvider;
-
- @Before
- public void setUp() throws Exception {
- mTestLooper = new TestLooper();
- mFakeVibrator = new FakeVibrator();
- mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper());
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
-
- ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
- when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
- when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
- when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
- when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
- when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
- when(mPackageManagerInternalMock.getSystemUiServiceComponent())
- .thenReturn(new ComponentName("", ""));
- doAnswer(invocation -> {
- mRegisteredPowerModeListener = invocation.getArgument(0);
- return null;
- }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
- addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
- addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
- }
-
- @After
- public void tearDown() throws Exception {
- InputManager.clearInstance();
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.removeServiceForTest(PowerManagerInternal.class);
- }
-
- private VibratorService createService() {
- VibratorService service = new VibratorService(mContextSpy,
- new VibratorService.Injector() {
- @Override
- VibratorController createVibratorController(
- VibratorController.OnVibrationCompleteListener listener) {
- return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener);
- }
-
- @Override
- Handler createHandler(Looper looper) {
- return new Handler(mTestLooper.getLooper());
- }
-
- @Override
- void addService(String name, IBinder service) {
- // ignore
- }
- });
- service.systemReady();
- return service;
- }
-
- @Test
- public void createService_initializesNativeService() {
- createService();
- assertTrue(mVibratorProvider.isInitialized());
- }
-
- @Test
- public void hasVibrator_withVibratorHalPresent_returnsTrue() {
- assertTrue(createService().hasVibrator());
- }
-
- @Test
- public void hasVibrator_withNoVibratorHalPresent_returnsFalse() {
- mVibratorProvider.disableVibrators();
- assertFalse(createService().hasVibrator());
- }
-
- @Test
- public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- assertTrue(createService().hasAmplitudeControl());
- }
-
- @Test
- public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
- assertFalse(createService().hasAmplitudeControl());
- }
-
- @Test
- public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- assertTrue(createService().hasAmplitudeControl());
- }
-
- @Test
- public void getVibratorInfo_returnsSameInfoFromNative() {
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
- IVibrator.CAP_AMPLITUDE_CONTROL);
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-
- VibratorInfo info = createService().getVibratorInfo();
- assertTrue(info.hasAmplitudeControl());
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_TICK));
- assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
- }
-
- @Test
- public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
- setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
- vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
- vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS);
-
- setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
- setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
- vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(2, effects.size());
- assertEquals(10, effects.get(0).getDuration());
- assertEquals(100, effects.get(1).getDuration());
- }
-
- @Test
- public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception {
- VibratorService service = createService();
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
- vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
- vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(3, effects.size());
- assertEquals(2, effects.get(0).getDuration());
- assertEquals(3, effects.get(1).getDuration());
- assertEquals(4, effects.get(2).getDuration());
- }
-
- @Test
- public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
- VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
- audioAttributes, effect).build();
-
- vibrate(service, effect, vibrationAttributes);
-
- verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
- }
-
- @Test
- public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
- new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_UNKNOWN).build());
-
- InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
- anyInt(), anyString());
- inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
- eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
- }
-
- @Test
- public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude()
- throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(1, effects.size());
- assertEquals(100, effects.get(0).getDuration());
- assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes());
- }
-
- @Test
- public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude()
- throws Exception {
- VibratorService service = createService();
- clearInvocations();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
- List<VibrationEffect> effects = mVibratorProvider.getEffects();
- assertEquals(1, effects.size());
- assertEquals(100, effects.get(0).getDuration());
- assertTrue(mVibratorProvider.getAmplitudes().isEmpty());
- }
-
- @Test
- public void vibrate_withPrebaked_performsEffect() throws Exception {
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- vibrateAndWait(service, effect, ALARM_ATTRS);
-
- VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
- VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
- throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), any(), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception {
- VibratorService service = createService();
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception {
- VibratorService service = createService();
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- // Settings callback is async, so wait before checking it never got cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception {
- VibratorService service = createService();
- vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
- // FakeSettingsProvider don't support testing triggering ContentObserver yet.
- service.updateVibrators();
-
- // Settings callback is async, so wait before checking it never got cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withComposed_performsEffect() throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- vibrateAndWait(service, effect, ALARM_ATTRS);
- assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
- .compose();
- vibrate(service, effect, ALARM_ATTRS);
- InOrder inOrderVerifier = inOrder(mIInputManagerMock);
- inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
- inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
- throws Exception {
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
- vibrateAndWait(service, effect, ALARM_ATTRS);
-
- assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes());
- assertEquals(
- Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)),
- mVibratorProvider.getEffects());
- }
-
- @Test
- public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
- when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
- setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
- /* timeout= */ 20));
- }
-
- @Test
- public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
- mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- // Trigger callbacks from controller.
- mTestLooper.moveTimeForward(50);
- mTestLooper.dispatchAll();
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception {
- VibratorService service = createService();
-
- vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- service.cancelVibrate(service);
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
- }
-
- @Test
- public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
- VibratorService service = createService();
- service.registerVibratorStateListener(mVibratorStateListenerMock);
-
- vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-
- InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
- // First notification done when listener is registered.
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verifyNoMoreInteractions();
- }
-
- @Test
- public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
- VibratorService service = createService();
-
- service.registerVibratorStateListener(mVibratorStateListenerMock);
-
- vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
- assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- service.unregisterVibratorStateListener(mVibratorStateListenerMock);
- // Trigger callbacks from controller.
- mTestLooper.moveTimeForward(50);
- mTestLooper.dispatchAll();
- assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
- InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
- // First notification done when listener is registered.
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
- inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
- inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister
- inOrderVerifier.verifyNoMoreInteractions();
- }
-
- @Test
- public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception {
- // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- mVibratorProvider.setSupportedEffects(
- VibrationEffect.EFFECT_CLICK,
- VibrationEffect.EFFECT_TICK,
- VibrationEffect.EFFECT_DOUBLE_CLICK,
- VibrationEffect.EFFECT_HEAVY_CLICK);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
- NOTIFICATION_ATTRS);
- vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
- List<Integer> playedStrengths = mVibratorProvider.getEffects().stream()
- .map(VibrationEffect.Prebaked.class::cast)
- .map(VibrationEffect.Prebaked::getEffectStrength)
- .collect(Collectors.toList());
- assertEquals(Arrays.asList(
- VibrationEffect.EFFECT_STRENGTH_STRONG,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM,
- VibrationEffect.EFFECT_STRENGTH_LIGHT),
- playedStrengths);
- }
-
- @Test
- public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception {
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
-
- mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- VibratorService service = createService();
-
- vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS);
- vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS);
- vibrateAndWait(service,
- VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
- HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS);
-
- List<Integer> amplitudes = mVibratorProvider.getAmplitudes();
- assertEquals(3, amplitudes.size());
- // Alarm vibration is never scaled.
- assertEquals(100, amplitudes.get(0).intValue());
- // Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertTrue(amplitudes.get(1) > 150);
- // Haptic feedback vibrations will be scaled with SCALE_LOW.
- assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50);
- }
-
- @Test
- public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception {
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
-
- mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- VibratorService service = createService();
-
- VibrationEffect effect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
- .compose();
-
- vibrateAndWait(service, effect, ALARM_ATTRS);
- vibrateAndWait(service, effect, NOTIFICATION_ATTRS);
- vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, effect, RINGTONE_ATTRS);
-
- List<VibrationEffect.Composition.PrimitiveEffect> primitives =
- mVibratorProvider.getEffects().stream()
- .map(VibrationEffect.Composed.class::cast)
- .map(VibrationEffect.Composed::getPrimitiveEffects)
- .flatMap(List::stream)
- .collect(Collectors.toList());
-
- // Ringtone vibration is off, so only the other 3 are propagated to native.
- assertEquals(6, primitives.size());
-
- // Alarm vibration is never scaled.
- assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2);
- assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2);
-
- // Notification vibrations will be scaled with SCALE_VERY_HIGH.
- assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2);
- assertTrue(0.7 < primitives.get(3).scale);
-
- // Haptic feedback vibrations will be scaled with SCALE_LOW.
- assertTrue(0.5 < primitives.get(4).scale);
- assertTrue(0.5 > primitives.get(5).scale);
- }
-
- private void vibrate(VibratorService service, VibrationEffect effect,
- VibrationAttributes attrs) {
- service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
- }
-
- private void vibrateAndWait(VibratorService service, VibrationEffect effect,
- VibrationAttributes attrs) throws Exception {
- CountDownLatch startedCount = new CountDownLatch(1);
- CountDownLatch finishedCount = new CountDownLatch(1);
- service.registerVibratorStateListener(new IVibratorStateListener() {
- @Override
- public void onVibrating(boolean vibrating) {
- if (vibrating) {
- startedCount.countDown();
- } else if (startedCount.getCount() == 0) {
- finishedCount.countDown();
- }
- }
-
- @Override
- public IBinder asBinder() {
- return mock(IBinder.class);
- }
- });
-
- mTestLooper.startAutoDispatch();
- service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
- assertTrue(startedCount.await(1, TimeUnit.SECONDS));
- assertTrue(finishedCount.await(1, TimeUnit.SECONDS));
- mTestLooper.stopAutoDispatchAndIgnoreExceptions();
- }
-
- private InputDevice createInputDeviceWithVibrator(int id) {
- return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
- null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
- false /* hasBattery */);
- }
-
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
-
- private void setRingerMode(int ringerMode) {
- AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
- audioManager.setRingerModeInternal(ringerMode);
- assertEquals(ringerMode, audioManager.getRingerModeInternal());
- }
-
- private void setUserSetting(String settingName, int value) {
- Settings.System.putIntForUser(
- mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
- }
-
- private void setGlobalSetting(String settingName, int value) {
- Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
- }
-
- private boolean waitUntil(Predicate<VibratorService> predicate,
- VibratorService service, long timeout) throws InterruptedException {
- long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
- boolean predicateResult = false;
- while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
- Thread.sleep(10);
- predicateResult = predicate.test(service);
- }
- return predicateResult;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
@@ -30,15 +28,12 @@
import android.hardware.power.stats.StateResidencyResult;
import android.power.PowerStatsInternal;
import android.util.SparseArray;
-import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
import org.junit.Before;
-import org.junit.Test;
import java.util.concurrent.CompletableFuture;
@@ -63,44 +58,6 @@
mBatteryStatsImpl);
}
- @Test
- public void getEnergyConsumptionData() {
- SparseLongArray expectSubsystems = new SparseLongArray();
- // Add some energy consumers used by BatteryExternalStatsWorker.
- final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
- "display");
- mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
- expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
- // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
- // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
- final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
- mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
- // Inform BESW that PowerStatsInternal is ready to query
- mBatteryExternalStatsWorker.systemServicesReady();
-
- MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
- assertEquals(expectSubsystems.size(), energies.size());
- final int size = expectSubsystems.size();
-
- for (int i = 0; i < size; i++) {
- int subsystem = expectSubsystems.keyAt(i);
- // find the subsystem in the returned MeasuredEnergyArray
- int subsystemIndex = -1;
- for (int j = 0; j < size; j++) {
- if (subsystem == energies.getSubsystem(i)) {
- subsystemIndex = i;
- break;
- }
- }
- assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
- subsystemIndex);
- assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
- }
- }
-
public class TestInjector extends BatteryExternalStatsWorker.Injector {
public TestInjector(Context context) {
super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
package com.android.server.am;
-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 com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
import android.util.SparseLongArray;
import androidx.test.filters.SmallTest;
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
-import org.junit.Before;
import org.junit.Test;
/**
@@ -38,134 +43,198 @@
*/
@SmallTest
public final class MeasuredEnergySnapshotTest {
- private static final int NUMBER_SUBSYSTEMS = 3;
- private static final int SUBSYSTEM_DISPLAY = 0;
- private static final int SUBSYSTEM_NEVER_USED = 1;
- private static final int SUBSYSTEM_CATAPULT = 2;
+ private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+ 0, 0, EnergyConsumerType.DISPLAY, "Display");
+ private static final EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+ 47, 0, EnergyConsumerType.OTHER, "GPU");
+ private static final EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+ 1, 1, EnergyConsumerType.OTHER, "HPU");
+ private static final EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+ 436, 2, EnergyConsumerType.OTHER, "IPU");
- private MeasuredEnergySnapshot mSnapshot;
+ private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+ CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+ private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+ CONSUMER_DISPLAY);
- // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
- private final int[] mAllSubsystems =
- {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
- // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
- private final int[] mSubsystemIndices = {0, 2, 1};
- private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
- private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return mAllSubsystems[index];
- }
-
- @Override
- public long getEnergy(int index) {
- return mCurrentSubsystemEnergyUJ[index];
- }
-
- @Override
- public int size() {
- return mAllSubsystems.length;
- }
+ // Elements in each results are purposefully out of order.
+ private static final EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+ // No CONSUMER_OTHER_2
};
- private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return mAllSubsystems[0];
- }
-
- @Override
- public long getEnergy(int index) {
- return mCurrentSubsystemEnergyUJ[0];
- }
-
- @Override
- public int size() {
- return 1;
- }
+ private static final EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+ createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+ };
+ private static final EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+ // No CONSUMER_OTHER_0
+ // No CONSUMER_OTHER_1
+ // No CONSUMER_OTHER_2
+ };
+ private static final EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+ // No CONSUMER_DISPLAY
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+ createEnergyConsumerResult(
+ CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+ createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+ };
+ private static final EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+ createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+ createEnergyConsumerResult(
+ CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+ // No CONSUMER_OTHER_1
+ createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
};
- @Before
- public void setUp() {
- mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+ @Test
+ public void testUpdateAndGetDelta_empty() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+ assertNull(snapshot.updateAndGetDelta(null));
+ assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
}
@Test
public void testUpdateAndGetDelta() {
- SparseLongArray result;
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
- // Increment DISPLAY by 15
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+ // results0
+ MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+ if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ }
- // Increment DISPLAY by 7
- // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
- result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
- assertEquals(1, result.size());
- assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+ // results1
+ delta = snapshot.updateAndGetDelta(RESULTS_1);
+ assertNotNull(delta);
+ assertEquals(24 - 14, delta.displayEnergyUJ);
- // Increment CATAPULT by 64 (in addition to the previous increase of 5)
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+ assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+ assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
- // Do nothing
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals("0 results should not appear at all", 0, result.size());
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
- // Increment DISPLAY by 42
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(1, result.size());
- assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+ // results2
+ delta = snapshot.updateAndGetDelta(RESULTS_2);
+ assertNotNull(delta);
+ assertEquals(36 - 24, delta.displayEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ assertNull(delta.otherTotalEnergyUJ);
- // Increment DISPLAY by 106 and CATAPULT by 13
- incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
- incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
- result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
- assertEquals(2, result.size());
- assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
- assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+ // results3
+ delta = snapshot.updateAndGetDelta(RESULTS_3);
+ assertNotNull(delta);
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+ assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+ assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+ assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+ assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+ assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+ // results4
+ delta = snapshot.updateAndGetDelta(RESULTS_4);
+ assertNotNull(delta);
+ assertEquals(43 - 36, delta.displayEnergyUJ);
+
+ assertNotNull(delta.otherTotalEnergyUJ);
+ assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+ assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+ assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+ assertNotNull(delta.otherUidEnergiesUJ);
+ assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+ assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+ assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+ assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+ assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
}
- private void incrementEnergyOfSubsystem(int subsystem, long energy) {
- mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+ /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+ @Test
+ public void testUpdateAndGetDelta_some() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+ // results0
+ MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+ if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+ assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ);
+ assertNull(delta.otherUidEnergiesUJ);
+ }
+
+ // results1
+ delta = snapshot.updateAndGetDelta(RESULTS_1);
+ assertNotNull(delta);
+ assertEquals(24 - 14, delta.displayEnergyUJ);
+ assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+ assertNull(delta.otherUidEnergiesUJ);
}
@Test
- public void testUpdateAndGetDelta_null() {
- assertNull(mSnapshot.updateAndGetDelta(null));
+ public void testGetNumOtherOrdinals() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+ assertEquals(3, snapshot.getNumOtherOrdinals());
}
@Test
- public void testHasSubsystem() {
- // Setup MeasuredEnergySnapshot which reported some of the subsystems.
- final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
- MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
- @Override
- public int getSubsystem(int index) {
- return subsystems[index];
- }
+ public void testGetNumOtherOrdinals_none() {
+ final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+ assertEquals(0, snapshot.getNumOtherOrdinals());
+ }
- @Override
- public long getEnergy(int index) {
- return 0; // Irrelevant for this test.
- }
+ private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+ final EnergyConsumer ec = new EnergyConsumer();
+ ec.id = id;
+ ec.ordinal = ord;
+ ec.type = type;
+ ec.name = name;
+ return ec;
+ }
- @Override
- public int size() {
- return subsystems.length;
- }
- };
- final MeasuredEnergySnapshot snapshot =
- new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+ private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+ final SparseArray<EnergyConsumer> map = new SparseArray<>();
+ for (EnergyConsumer ec : ecs) {
+ map.put(ec.id, ec);
+ }
+ return map;
+ }
- assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
- assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
- assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+ private static EnergyConsumerResult createEnergyConsumerResult(
+ int id, long energyUWs, int[] uids, long[] uidEnergies) {
+ final EnergyConsumerResult ecr = new EnergyConsumerResult();
+ ecr.id = id;
+ ecr.energyUWs = energyUWs;
+ if (uids != null) {
+ ecr.attribution = new EnergyConsumerAttribution[uids.length];
+ for (int i = 0; i < uids.length; i++) {
+ ecr.attribution[i] = new EnergyConsumerAttribution();
+ ecr.attribution[i].uid = uids[i];
+ ecr.attribution[i].energyUWs = uidEnergies[i];
+ }
+ }
+ return ecr;
+ }
+
+ private void assertNullOrEmpty(SparseLongArray a) {
+ if (a != null) assertEquals("Array should be null or empty", 0, a.size());
}
}
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 1a22661..a078a77 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -52,7 +52,8 @@
public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
- private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
+ // A device state that is not reported as being supported for the default test provider.
+ private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
new file mode 100644
index 0000000..b5c8053
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceState}.
+ * <p/>
+ * Run with <code>atest DeviceStateTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateTest {
+ @Test
+ public void testConstruct() {
+ final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
+ "CLOSED" /* name */);
+ assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
+ assertEquals(state.getName(), "CLOSED");
+ }
+
+ @Test
+ public void testConstruct_nullName() {
+ final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
+ null /* name */);
+ assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
+ assertNull(state.getName());
+ }
+
+ @Test
+ public void testConstruct_tooLargeIdentifier() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
+ null /* name */);
+ });
+ }
+
+ @Test
+ public void testConstruct_tooSmallIdentifier() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
+ null /* name */);
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
index 86054e4..27fce3c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -18,13 +18,17 @@
import static com.google.common.truth.Truth.assertThat;
+import android.graphics.FontListParser;
import android.platform.test.annotations.Presubmit;
+import android.text.FontConfig;
+import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -38,13 +42,19 @@
public final class PersistentSystemFontConfigTest {
@Test
- public void testWriteRead() throws IOException, XmlPullParserException {
+ public void testWriteRead() throws Exception {
long expectedModifiedDate = 1234567890;
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
config.lastModifiedDate = expectedModifiedDate;
config.updatedFontDirs.add("~~abc");
config.updatedFontDirs.add("~~def");
+ FontConfig.FontFamily fontFamily = parseFontFamily(
+ "<family name='test'>"
+ + " <font>test.ttf</font>"
+ + "</family>");
+ config.fontFamilies.add(fontFamily);
+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
PersistentSystemFontConfig.writeToXml(baos, config);
@@ -57,6 +67,7 @@
assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
+ assertThat(another.fontFamilies).containsExactly(fontFamily);
}
}
}
@@ -75,4 +86,11 @@
}
}
+ private static FontConfig.FontFamily parseFontFamily(String xml) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+ parser.setInput(is, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readFamily(parser, "", null);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index cb83b0f..4bbf96f 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,12 +22,15 @@
import static org.junit.Assert.fail;
import android.content.Context;
+import android.graphics.FontListParser;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import android.system.Os;
+import android.text.FontConfig;
+import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -37,7 +40,9 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -49,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
@Presubmit
@SmallTest
@@ -157,7 +163,11 @@
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
- newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + " <font>bar.ttf</font>"
+ + "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -173,6 +183,14 @@
assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4);
// Outdated font dir should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
+ assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
+ assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(foobar.getFontList()).hasSize(2);
+ assertThat(foobar.getFontList().get(0).getFile())
+ .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
+ assertThat(foobar.getFontList().get(1).getFile())
+ .isEqualTo(dir.getFontFileMap().get("bar.ttf"));
}
@Test
@@ -184,6 +202,7 @@
mConfigFile);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -198,7 +217,11 @@
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
- newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + " <font>bar.ttf</font>"
+ + "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -211,6 +234,7 @@
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -225,7 +249,11 @@
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
- newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + " <font>bar.ttf</font>"
+ + "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -239,6 +267,7 @@
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -253,7 +282,11 @@
newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
- newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+ newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + " <font>bar.ttf</font>"
+ + "</family>")));
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
@@ -274,6 +307,8 @@
// We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled
// fonts.
assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+ // Font family depending on obsoleted font should be removed.
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -285,6 +320,7 @@
new File("/dev/null"));
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
+ assertThat(dir.getFontFamilyMap()).isEmpty();
}
@Test
@@ -295,12 +331,19 @@
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
mConfigFile);
dirForPreparation.loadFontFileMap();
- dirForPreparation.update(
- Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+ dirForPreparation.update(Arrays.asList(
+ newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + "</family>")));
try {
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
- newFontUpdateRequest("bar,2", "Invalid signature")));
+ newFontUpdateRequest("bar,2", "Invalid signature"),
+ newAddFontFamilyRequest("<family name='foobar'>"
+ + " <font>foo.ttf</font>"
+ + " <font>bar.ttf</font>"
+ + "</family>")));
fail("Batch update with invalid signature should fail");
} catch (FontManagerService.SystemFontException e) {
// Expected
@@ -313,6 +356,11 @@
// The state should be rolled back as a whole if one of the update requests fail.
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+ assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(foobar.getFontList()).hasSize(1);
+ assertThat(foobar.getFontList().get(0).getFile())
+ .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
}
@Test
@@ -364,7 +412,7 @@
dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
try {
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
- fail("Expect IllegalArgumentException");
+ fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
}
@@ -440,7 +488,7 @@
try {
dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
- fail("Expect IllegalArgumentException");
+ fail("Expect SystemFontException");
} catch (FontManagerService.SystemFontException e) {
assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
}
@@ -599,6 +647,127 @@
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
}
+ @Test
+ public void addFontFamily() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+
+ dir.update(Arrays.asList(
+ newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='test'>"
+ + " <font>test.ttf</font>"
+ + "</family>")));
+ assertThat(dir.getFontFileMap()).containsKey("test.ttf");
+ assertThat(dir.getFontFamilyMap()).containsKey("test");
+ FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+ assertThat(test.getFontList()).hasSize(1);
+ assertThat(test.getFontList().get(0).getFile())
+ .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+ }
+
+ @Test
+ public void addFontFamily_noName() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+
+ try {
+ dir.update(Arrays.asList(
+ newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family lang='en'>"
+ + " <font>test.ttf</font>"
+ + "</family>")));
+ fail("Expect IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expect
+ }
+ }
+
+ @Test
+ public void addFontFamily_fontNotAvailable() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+
+ try {
+ dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
+ + " <font>test.ttf</font>"
+ + "</family>")));
+ fail("Expect IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expect
+ }
+ }
+
+ @Test
+ public void getSystemFontConfig() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+ // We assume we have monospace.
+ assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
+
+ dir.update(Arrays.asList(
+ newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+ // Updating an existing font family.
+ newAddFontFamilyRequest("<family name='monospace'>"
+ + " <font>test.ttf</font>"
+ + "</family>"),
+ // Adding a new font family.
+ newAddFontFamilyRequest("<family name='test'>"
+ + " <font>test.ttf</font>"
+ + "</family>")));
+ FontConfig fontConfig = dir.getSystemFontConfig();
+ assertNamedFamilyExists(fontConfig, "monospace");
+ FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace");
+ assertThat(monospace.getFontList()).hasSize(1);
+ assertThat(monospace.getFontList().get(0).getFile())
+ .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+ assertNamedFamilyExists(fontConfig, "test");
+ assertThat(getLastFamily(fontConfig, "test").getFontList())
+ .isEqualTo(monospace.getFontList());
+ }
+
+ @Test
+ public void getSystemFontConfig_preserveFirstFontFamily() throws Exception {
+ FakeFontFileParser parser = new FakeFontFileParser();
+ FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+ UpdatableFontDir dir = new UpdatableFontDir(
+ mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+ mConfigFile);
+ dir.loadFontFileMap();
+ assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+ FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
+ assertThat(firstFontFamily.getName()).isNotEmpty();
+
+ dir.update(Arrays.asList(
+ newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+ newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+ + " <font>test.ttf</font>"
+ + "</family>")));
+ FontConfig fontConfig = dir.getSystemFontConfig();
+ assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+ assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
+ FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+ assertThat(updated.getFontList()).hasSize(1);
+ assertThat(updated.getFontList().get(0).getFile())
+ .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+ assertThat(updated).isNotEqualTo(firstFontFamily);
+ }
+
private FontUpdateRequest newFontUpdateRequest(String content, String signature)
throws Exception {
File file = File.createTempFile("font", "ttf", mCacheDir);
@@ -608,10 +777,36 @@
signature.getBytes());
}
+ private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+ parser.setInput(is, "UTF-8");
+ parser.nextTag();
+ FontConfig.FontFamily fontFamily = FontListParser.readFamily(parser, "", null);
+ return new FontUpdateRequest(fontFamily);
+ }
+
private void writeConfig(PersistentSystemFontConfig.Config config,
File file) throws IOException {
try (FileOutputStream fos = new FileOutputStream(file)) {
PersistentSystemFontConfig.writeToXml(fos, config);
}
}
+
+ // Returns the last family with the given name, which will be used for creating Typeface.
+ private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
+ List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
+ for (int i = fontFamilies.size() - 1; i >= 0; i--) {
+ if (familyName.equals(fontFamilies.get(i).getName())) {
+ return fontFamilies.get(i);
+ }
+ }
+ return null;
+ }
+
+ private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
+ assertThat(fontConfig.getFontFamilies().stream()
+ .map(FontConfig.FontFamily::getName)
+ .collect(Collectors.toSet())).contains(familyName);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
new file mode 100644
index 0000000..b8dfd56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ActiveSourceAction} */
+@SmallTest
+@RunWith(JUnit4.class)
+public class PowerStatusMonitorActionTest {
+
+ private Context mContextSpy;
+ private HdmiControlService mHdmiControlService;
+ private FakeNativeWrapper mNativeWrapper;
+
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private int mPhysicalAddress;
+ private HdmiCecLocalDeviceTv mTvDevice;
+
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalService mIThermalServiceMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+ when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+
+ mHdmiControlService = new HdmiControlService(mContextSpy) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public void setWiredDeviceConnectionState(
+ int type, int state, String address, String name) {
+ // Do nothing.
+ }
+ };
+ }
+
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ boolean isPowerStandby() {
+ return false;
+ }
+
+ @Override
+ protected PowerManager getPowerManager() {
+ return powerManager;
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+
+ @Override
+ protected HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
+ };
+
+ Looper looper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(looper);
+ mNativeWrapper = new FakeNativeWrapper();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(hdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
+ mTvDevice.init();
+ mLocalDevices.add(mTvDevice);
+ mTestLooper.dispatchAll();
+ HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
+ hdmiPortInfo[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+ hdmiPortInfo[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfo);
+ mHdmiControlService.initService();
+ mPhysicalAddress = 0x0000;
+ mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void sourceDevice_1_4_updatesPowerState() {
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON);
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+
+ mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY);
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY);
+ }
+
+ private void assertPowerStatus(int logicalAddress, int powerStatus) {
+ HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(
+ logicalAddress);
+ assertThat(deviceInfo).isNotNull();
+ assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+ }
+
+ @Test
+ public void sourceDevice_2_0_doesNotUpdatePowerState() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+ mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+ mTestLooper.dispatchAll();
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+ }
+
+ @Test
+ public void mixedSourceDevices_localDevice_1_4_updatesAll() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+ mTestLooper.dispatchAll();
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+ reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+ assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+ assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_2);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
+ }
+
+ @Test
+ public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mTestLooper.dispatchAll();
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+ sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+ reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+ PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+ action.start();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_1);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+ HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ ADDR_TV,
+ ADDR_PLAYBACK_2);
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
+ }
+
+ private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) {
+ HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mNativeWrapper.onCecMessage(playbackDevice);
+ mTestLooper.dispatchAll();
+ }
+
+ private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
+ int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+ HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+ logicalAddress, destination,
+ powerStatus);
+ mNativeWrapper.onCecMessage(reportPowerStatus);
+ mTestLooper.dispatchAll();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 353ac4b..f9b25d9 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -39,6 +39,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -51,7 +52,7 @@
private static final String TAG = "WorkerCountTrackerTest";
private static final double[] EQUAL_PROBABILITY_CDF =
- buildCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+ buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
1.0 / NUM_WORK_TYPES);
private Random mRandom;
@@ -64,18 +65,19 @@
}
@NonNull
- private static double[] buildCdf(double pTop, double pEj, double pBg, double pBgUser) {
- double[] cdf = new double[JobConcurrencyManager.NUM_WORK_TYPES];
+ private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) {
+ return buildCdf(pTop, pEj, pBg, pBgUser);
+ }
+
+ @NonNull
+ private static double[] buildCdf(double... probs) {
+ double[] cdf = new double[probs.length];
double sum = 0;
- sum += pTop;
- cdf[0] = sum;
- sum += pEj;
- cdf[1] = sum;
- sum += pBg;
- cdf[2] = sum;
- sum += pBgUser;
- cdf[3] = sum;
+ for (int i = 0; i < probs.length; ++i) {
+ sum += probs[i];
+ cdf[i] = sum;
+ }
if (Double.compare(1, sum) != 0) {
throw new IllegalArgumentException("probabilities don't sum to one: " + sum);
@@ -83,25 +85,30 @@
return cdf;
}
- @JobConcurrencyManager.WorkType
- static int getRandomWorkType(double[] cdf, double rand) {
+ static int getRandomIndex(double[] cdf, double rand) {
for (int i = cdf.length - 1; i >= 0; --i) {
if (rand < cdf[i] && (i == 0 || rand > cdf[i - 1])) {
- switch (i) {
- case 0:
- return WORK_TYPE_TOP;
- case 1:
- return WORK_TYPE_EJ;
- case 2:
- return WORK_TYPE_BG;
- case 3:
- return WORK_TYPE_BGUSER;
- default:
- throw new IllegalStateException("Unknown work type");
- }
+ return i;
}
}
- throw new IllegalStateException("Couldn't pick random work type");
+ throw new IllegalStateException("Couldn't pick random index");
+ }
+
+ @JobConcurrencyManager.WorkType
+ static int getRandomWorkType(double[] cdf, double rand) {
+ final int index = getRandomIndex(cdf, rand);
+ switch (index) {
+ case 0:
+ return WORK_TYPE_TOP;
+ case 1:
+ return WORK_TYPE_EJ;
+ case 2:
+ return WORK_TYPE_BG;
+ case 3:
+ return WORK_TYPE_BGUSER;
+ default:
+ throw new IllegalStateException("Unknown work type");
+ }
}
/**
@@ -110,25 +117,59 @@
class Jobs {
public final SparseIntArray running = new SparseIntArray();
public final SparseIntArray pending = new SparseIntArray();
+ public final List<Integer> pendingMultiTypes = new ArrayList<>();
- public void maybeEnqueueJobs(double probStart, double[] typeCdf) {
+ /**
+ * @param probStart Probability of starting a job
+ * @param typeCdf The CDF representing the probability of each work type
+ * @param numTypesCdf The CDF representing the probability of a job having X different
+ * work types. Each index i represents i+1 work types (ie. index 0 = 1
+ * work type, index 3 = 4 work types).
+ */
+ public void maybeEnqueueJobs(double probStart, double[] typeCdf, double[] numTypesCdf) {
+ assertThat(numTypesCdf.length).isAtMost(NUM_WORK_TYPES);
+ assertThat(numTypesCdf.length).isAtLeast(1);
+
while (mRandom.nextDouble() < probStart) {
- final int workType = getRandomWorkType(typeCdf, mRandom.nextDouble());
- pending.put(workType, pending.get(workType) + 1);
+ final int numTypes = getRandomIndex(numTypesCdf, mRandom.nextDouble()) + 1;
+ int types = WORK_TYPE_NONE;
+ for (int i = 0; i < numTypes; ++i) {
+ types |= getRandomWorkType(typeCdf, mRandom.nextDouble());
+ }
+ addPending(types, 1);
}
}
- public void maybeFinishJobs(double probStop) {
- for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
- if (mRandom.nextDouble() < probStop) {
- running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
- mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+ void addPending(int allWorkTypes, int num) {
+ for (int n = 0; n < num; ++n) {
+ for (int i = 0; i < 32; ++i) {
+ final int type = 1 << i;
+ if ((allWorkTypes & type) != 0) {
+ pending.put(type, pending.get(type) + 1);
+ }
+ }
+ pendingMultiTypes.add(allWorkTypes);
+ }
+ }
+
+ void removePending(int allWorkTypes) {
+ for (int i = 0; i < 32; ++i) {
+ final int type = 1 << i;
+ if ((allWorkTypes & type) != 0) {
+ pending.put(type, pending.get(type) - 1);
}
}
- for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
- if (mRandom.nextDouble() < probStop) {
- running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
- mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+ pendingMultiTypes.remove(Integer.valueOf(allWorkTypes));
+ }
+
+ public void maybeFinishJobs(double probStop) {
+ for (int i = running.size() - 1; i >= 0; --i) {
+ final int workType = running.keyAt(i);
+ for (int c = running.valueAt(i); c > 0; --c) {
+ if (mRandom.nextDouble() < probStop) {
+ running.put(workType, running.get(workType) - 1);
+ mWorkCountTracker.onJobFinished(workType);
+ }
}
}
}
@@ -171,6 +212,15 @@
return false;
}
+ private int getPendingMultiType(Jobs jobs, @JobConcurrencyManager.WorkType int workType) {
+ for (int multiType : jobs.pendingMultiTypes) {
+ if ((multiType & workType) != 0) {
+ return multiType;
+ }
+ }
+ throw new IllegalStateException("No pending multi type with work type: " + workType);
+ }
+
private void startPendingJobs(Jobs jobs) {
while (hasStartablePendingJob(jobs)) {
final int startingWorkType =
@@ -178,9 +228,10 @@
if (jobs.pending.get(startingWorkType) > 0
&& mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
- jobs.pending.put(startingWorkType, jobs.pending.get(startingWorkType) - 1);
+ final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+ jobs.removePending(pendingMultiType);
jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
- mWorkCountTracker.stageJob(startingWorkType);
+ mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
mWorkCountTracker.onJobStarted(startingWorkType);
}
}
@@ -192,12 +243,17 @@
private void checkRandom(Jobs jobs, int numTests, int totalMax,
@NonNull List<Pair<Integer, Integer>> minLimits,
@NonNull List<Pair<Integer, Integer>> maxLimits,
- double probStart, double[] typeCdf, double probStop) {
+ double probStart, double[] typeCdf, double[] numTypesCdf, double probStop) {
+ int minExpected = 0;
+ for (Pair<Integer, Integer> minLimit : minLimits) {
+ minExpected = Math.min(minLimit.second, minExpected);
+ }
for (int i = 0; i < numTests; i++) {
jobs.maybeFinishJobs(probStop);
- jobs.maybeEnqueueJobs(probStart, typeCdf);
+ jobs.maybeEnqueueJobs(probStart, typeCdf, numTypesCdf);
recount(jobs, totalMax, minLimits, maxLimits);
+ final int numPending = jobs.pendingMultiTypes.size();
startPendingJobs(jobs);
int totalRunning = 0;
@@ -209,6 +265,7 @@
totalRunning += numRunning;
}
assertThat(totalRunning).isAtMost(totalMax);
+ assertThat(totalRunning).isAtLeast(Math.min(minExpected, numPending));
for (Pair<Integer, Integer> maxLimit : maxLimits) {
assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
.that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
@@ -233,7 +290,7 @@
final double probStart = 0.1;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
- EQUAL_PROBABILITY_CDF, probStop);
+ EQUAL_PROBABILITY_CDF, EQUAL_PROBABILITY_CDF, probStop);
}
@Test
@@ -246,10 +303,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.5, 0, 0.5, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0);
+ final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -262,10 +321,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] numTypesCdf = buildCdf(.75, .2, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -278,10 +339,12 @@
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final List<Pair<Integer, Integer>> minLimits = List.of();
final double probStop = 0.5;
- final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+ final double[] numTypesCdf = buildCdf(.05, .95);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -294,10 +357,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.1, 0, 0.8, .1);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1);
+ final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -310,10 +375,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0.9, 0, 0.1, 0);
+ final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -326,10 +393,12 @@
List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.1, 0, 0.1, .8);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8);
+ final double[] numTypesCdf = buildCdf(0.5, 0.5);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -343,10 +412,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.9, 0, 0.05, 0.05);
+ final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -360,10 +431,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.5, 0.5);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -377,10 +450,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.1, 0.9);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9);
+ final double[] numTypesCdf = buildCdf(0.9, 0.1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -394,10 +469,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
final double probStop = 0.5;
- final double[] cdf = buildCdf(0, 0, 0.9, 0.1);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -410,10 +487,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.5, 0.5, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0);
+ final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -430,10 +509,11 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1));
final double probStop = 0.01;
+ final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9);
final double probStart = 0.99;
checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
- EQUAL_PROBABILITY_CDF, probStop);
+ EQUAL_PROBABILITY_CDF, numTypesCdf, probStop);
}
@Test
@@ -446,10 +526,12 @@
List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4));
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(.1, 0.5, 0.35, 0.05);
+ final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05);
+ final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
@Test
@@ -464,10 +546,12 @@
final List<Pair<Integer, Integer>> minLimits =
List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2));
final double probStop = 0.4;
- final double[] cdf = buildCdf(0.01, 0.49, 0.1, 0.4);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4);
+ final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
- checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+ checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+ cdf, numTypesCdf, probStop);
}
/** Used by the following tests */
@@ -483,7 +567,7 @@
jobs.running.put(run.first, run.second);
}
for (Pair<Integer, Integer> pend : pending) {
- jobs.pending.put(pend.first, pend.second);
+ jobs.addPending(pend.first, pend.second);
}
recount(jobs, totalMax, minLimits, maxLimits);
@@ -651,14 +735,32 @@
Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
/* resPen */ List.of(
Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
+
+ // Test multi-types
+ checkSimple(6,
+ /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+ /* max */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* run */ List.of(),
+ /* pen */ List.of(
+ // 2 of these as TOP, 1 as EJ
+ Pair.create(WORK_TYPE_TOP | WORK_TYPE_EJ, 3),
+ // 1 as EJ, 2 as BG
+ Pair.create(WORK_TYPE_EJ | WORK_TYPE_BG, 3),
+ Pair.create(WORK_TYPE_BG, 4),
+ Pair.create(WORK_TYPE_BGUSER, 1)),
+ /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2),
+ Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+ /* resPen */ List.of(
+ Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)));
}
/** Tests that the counter updates properly when jobs are stopped. */
@Test
public void testJobLifecycleLoop() {
final Jobs jobs = new Jobs();
- jobs.pending.put(WORK_TYPE_TOP, 11);
- jobs.pending.put(WORK_TYPE_BG, 10);
+ jobs.addPending(WORK_TYPE_TOP, 11);
+ jobs.addPending(WORK_TYPE_BG, 10);
final int totalMax = 6;
final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
@@ -729,4 +831,149 @@
assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
}
+
+ /** Tests that the counter updates properly when jobs are stopped. */
+ @Test
+ public void testJobLifecycleLoop_Multitype() {
+ final Jobs jobs = new Jobs();
+ jobs.addPending(WORK_TYPE_TOP, 6); // a
+ jobs.addPending(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+ jobs.addPending(WORK_TYPE_BG, 10); // c
+
+ final int totalMax = 8;
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+ recount(jobs, totalMax, minLimits, maxLimits);
+
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(5);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+ // Since starting happens in random order, all EJs could have run first.
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+ // Stop all jobs
+ jobs.maybeFinishJobs(1);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+ // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+ // for EJ, which would reduce BG count to 3 instead of 4.
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(5);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(6);
+
+ // Stop only a bg job and make sure the counter only allows another bg job to start.
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+ // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+ // for EJ, which would reduce BG count to 3 instead of 4.
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(4);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(5);
+ }
+
+ /** Tests that the counter updates properly when jobs are stopped. */
+ @Test
+ public void testJobLifecycleLoop_Multitype_RandomOrder() {
+ final Jobs jobs = new Jobs();
+ SparseIntArray multiToCount = new SparseIntArray();
+ multiToCount.put(WORK_TYPE_TOP, 6); // a
+ multiToCount.put(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+ multiToCount.put(WORK_TYPE_EJ | WORK_TYPE_BG, 5); // c
+ multiToCount.put(WORK_TYPE_BG, 5); // d
+ while (multiToCount.size() > 0) {
+ final int index = mRandom.nextInt(multiToCount.size());
+ final int count = multiToCount.valueAt(index);
+ jobs.addPending(multiToCount.keyAt(index), 1);
+ if (count <= 1) {
+ multiToCount.removeAt(index);
+ } else {
+ multiToCount.put(multiToCount.keyAt(index), count - 1);
+ }
+ }
+
+ final int totalMax = 8;
+ final List<Pair<Integer, Integer>> minLimits =
+ List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+ final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+ recount(jobs, totalMax, minLimits, maxLimits);
+
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(10);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+ startPendingJobs(jobs);
+
+ // Random order, but we should have 6 TOP, 1 EJ, and 1 BG running.
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+ // Can't equate pending EJ since some could be running as TOP and BG
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+ // Stop all jobs
+ jobs.maybeFinishJobs(1);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ // Random order, but we should have 4 TOP, 1 EJ, and 1 BG running.
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ // At this point, all TOP should be running (or have already run).
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(5);
+
+ // Stop only a bg job and make sure the counter only allows another bg job to start.
+ jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+ mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_NONE);
+ assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+ startPendingJobs(jobs);
+
+ assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+ assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+ assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+ assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+ assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index 25dbc6b..33ea710 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -45,6 +45,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,8 @@
MockitoAnnotations.initMocks(this);
final Context context = InstrumentationRegistry.getTargetContext();
mUserId = ActivityManager.getCurrentUser();
- mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+ mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0,
+ Process.SHELL_UID);
when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index a4ba4c8..a896f1b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -43,6 +43,7 @@
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserManager;
@@ -155,6 +156,11 @@
}
@Override
+ void post(Handler handler, Runnable runnable) {
+ runnable.run();
+ }
+
+ @Override
public UserManager getUserManager() {
return mUserManager;
}
@@ -369,7 +375,7 @@
@Test
public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
}
@Test
@@ -401,7 +407,7 @@
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -435,7 +441,7 @@
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mServiceConnection).unwrap(any(), anyLong());
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -466,7 +472,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
verify(mInjected, never()).reportMetric(anyBoolean());
}
@@ -493,7 +499,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected, never()).reportMetric(anyBoolean());
}
@@ -527,7 +533,7 @@
when(mInjected.getBootCount()).thenReturn(10);
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mInjected).reportMetric(eq(true));
}
@@ -557,7 +563,7 @@
ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
- mService.loadRebootEscrowDataIfAvailable();
+ mService.loadRebootEscrowDataIfAvailable(null);
verify(mRebootEscrow).retrieveKey();
assertFalse(metricsSuccessCaptor.getValue());
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
index bc1e025..28b737b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -42,7 +43,6 @@
import org.mockito.stubbing.Answer;
import java.io.File;
-import java.io.IOException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -130,7 +130,7 @@
@Test
public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
- doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+ doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong());
assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 3a292de..38125c7 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -21,7 +21,9 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.os.Process
+import android.util.ArrayMap
import com.android.server.om.OverlayActorEnforcer.ActorState
+import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
@@ -29,6 +31,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import org.mockito.Mockito
import org.mockito.Mockito.spy
import java.io.IOException
@@ -125,11 +128,11 @@
ActorState.TARGET_NOT_FOUND withCases {
failure("nullPkgInfo") { targetPkgInfo = null }
allowed("debuggable") {
- targetPkgInfo = pkgInfo(TARGET_PKG).apply {
- applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE
+ targetPkgInfo = androidPackage(TARGET_PKG).apply {
+ whenever(this.isDebuggable).thenReturn(true)
}
}
- skip { targetPkgInfo = pkgInfo(TARGET_PKG) }
+ skip { targetPkgInfo = androidPackage(TARGET_PKG) }
},
ActorState.NO_PACKAGES_FOR_UID withCases {
failure("empty") { callingUid = EMPTY_UID }
@@ -236,22 +239,20 @@
mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG))
}
},
- ActorState.MISSING_APP_INFO withCases {
+ ActorState.ACTOR_NOT_FOUND withCases {
failure("nullActorPkgInfo") { actorPkgInfo = null }
failure("nullActorAppInfo") {
- actorPkgInfo = PackageInfo().apply { applicationInfo = null }
+ actorPkgInfo = null
}
- skip { actorPkgInfo = pkgInfo(VALID_ACTOR_PKG) }
+ skip { actorPkgInfo = androidPackage(VALID_ACTOR_PKG) }
},
ActorState.ACTOR_NOT_PREINSTALLED withCases {
failure("notSystem") {
- actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
- applicationInfo.flags = 0
- }
+ actorPkgInfo = androidPackage(VALID_ACTOR_PKG)
}
skip {
- actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
- applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM
+ actorPkgInfo = androidPackage(VALID_ACTOR_PKG).apply {
+ whenever(this.isSystem).thenReturn(true)
}
}
},
@@ -272,22 +273,22 @@
) {
fun toOverlayInfo() = OverlayInfo(
OVERLAY_PKG,
+ "",
targetPackageName,
targetOverlayableName,
null,
"/path",
OverlayInfo.STATE_UNKNOWN, 0,
- 0, false)
+ 0, false, false)
}
private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
TestCase(this).apply(block)
- private fun pkgInfo(pkgName: String): PackageInfo = mockThrowOnUnmocked {
- this.packageName = pkgName
- this.applicationInfo = ApplicationInfo().apply {
- this.packageName = pkgName
- }
+ private fun androidPackage(pkgName: String): AndroidPackage = mockThrowOnUnmocked {
+ whenever(this.packageName).thenReturn(pkgName)
+ whenever(this.isDebuggable).thenReturn(false)
+ whenever(this.isSystem).thenReturn(false)
}
private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
@@ -363,8 +364,8 @@
var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
var hasPermission: Boolean = false,
var targetOverlayableInfo: OverlayableInfo? = null,
- var targetPkgInfo: PackageInfo? = null,
- var actorPkgInfo: PackageInfo? = null,
+ var targetPkgInfo: AndroidPackage? = null,
+ var actorPkgInfo: AndroidPackage? = null,
vararg val packageNames: String = arrayOf("com.test.actor.one")
) : PackageManagerHelper {
@@ -375,6 +376,14 @@
override fun getNamedActors() = namedActorsMap
+ override fun isInstantApp(packageName: String, userId: Int): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun initializeForUser(userId: Int): ArrayMap<String, AndroidPackage> {
+ throw UnsupportedOperationException()
+ }
+
@Throws(IOException::class)
override fun getOverlayableForTarget(
packageName: String,
@@ -394,9 +403,6 @@
else -> null
}
- override fun getPackageInfo(packageName: String, userId: Int) =
- listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
-
@Throws(IOException::class) // Mockito requires this checked exception to be declared
override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
return targetOverlayableInfo?.takeIf {
@@ -411,11 +417,10 @@
}
}
- override fun getConfigSignaturePackage(): String {
- throw UnsupportedOperationException()
- }
+ override fun getPackageForUser(packageName: String, userId: Int) =
+ listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
- override fun getOverlayPackages(userId: Int): MutableList<PackageInfo> {
+ override fun getConfigSignaturePackage(): String {
throw UnsupportedOperationException()
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 5468fba..55cd772 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -21,7 +21,9 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
@@ -29,77 +31,66 @@
import org.junit.runner.RunWith;
import java.util.Arrays;
-import java.util.List;
import java.util.function.BiConsumer;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceImplTestsBase {
private static final String OVERLAY = "com.test.overlay";
+ private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
private static final String TARGET = "com.test.target";
private static final int USER = 0;
private static final String OVERLAY2 = OVERLAY + "2";
+ private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
@Test
public void testUpdateOverlaysForUser() {
final OverlayManagerServiceImpl impl = getImpl();
+ final String otherTarget = "some.other.target";
addPackage(target(TARGET), USER);
- addPackage(target("some.other.target"), USER);
+ addPackage(target(otherTarget), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
// do nothing, expect no change
- final List<String> a = impl.updateOverlaysForUser(USER);
- assertEquals(1, a.size());
- assertTrue(a.contains(TARGET));
+ final ArraySet<PackageAndUser> a = impl.updateOverlaysForUser(USER);
+ assertEquals(3, a.size());
+ assertTrue(a.containsAll(Arrays.asList(
+ new PackageAndUser(TARGET, USER),
+ new PackageAndUser(otherTarget, USER),
+ new PackageAndUser(OVERLAY, USER))));
- // upgrade overlay, keep target
- addPackage(overlay(OVERLAY, TARGET), USER);
-
- final List<String> b = impl.updateOverlaysForUser(USER);
- assertEquals(1, b.size());
- assertTrue(b.contains(TARGET));
-
- // do nothing, expect no change
- final List<String> c = impl.updateOverlaysForUser(USER);
- assertEquals(1, c.size());
- assertTrue(c.contains(TARGET));
-
- // upgrade overlay, switch to new target
- addPackage(overlay(OVERLAY, "some.other.target"), USER);
- final List<String> d = impl.updateOverlaysForUser(USER);
- assertEquals(2, d.size());
- assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
-
- // do nothing, expect no change
- final List<String> f = impl.updateOverlaysForUser(USER);
- assertEquals(1, f.size());
- assertTrue(f.contains("some.other.target"));
+ final ArraySet<PackageAndUser> b = impl.updateOverlaysForUser(USER);
+ assertEquals(3, b.size());
+ assertTrue(b.containsAll(Arrays.asList(
+ new PackageAndUser(TARGET, USER),
+ new PackageAndUser(otherTarget, USER),
+ new PackageAndUser(OVERLAY, USER))));
}
@Test
public void testImmutableEnabledChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertFalse(o1.isMutable);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o2);
assertTrue(o2.isEnabled());
assertFalse(o2.isMutable);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertFalse(o3.isEnabled());
assertFalse(o3.isMutable);
@@ -108,26 +99,26 @@
@Test
public void testMutableEnabledChangeHasNoEffect() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertTrue(o1.isMutable);
configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o2);
assertFalse(o2.isEnabled());
assertTrue(o2.isMutable);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertFalse(o3.isEnabled());
assertTrue(o3.isMutable);
@@ -136,13 +127,13 @@
@Test
public void testMutableEnabledToImmutableEnabled() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o);
assertEquals(enabled, o.isEnabled());
assertEquals(mutable, o.isMutable);
@@ -180,38 +171,38 @@
@Test
public void testMutablePriorityChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertFalse(o1.isEnabled());
- final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+ final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
assertFalse(o2.isEnabled());
// Overlay priority changing between reboots should not affect enable state of mutable
// overlays.
- impl.setEnabled(OVERLAY, true, USER);
+ impl.setEnabled(IDENTIFIER, true, USER);
// Reorder the overlays
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
- final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+ final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertFalse(o4.isEnabled());
@@ -220,19 +211,19 @@
@Test
public void testImmutablePriorityChange() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertTrue(o1.isEnabled());
- final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+ final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
assertTrue(o2.isEnabled());
@@ -242,12 +233,12 @@
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
- final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+ final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertTrue(o4.isEnabled());
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index 33dbcc0..45f82a3 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.util.Pair;
@@ -39,19 +40,23 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
private static final String OVERLAY = "com.test.overlay";
+ private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
private static final String TARGET = "com.test.target";
private static final int USER = 0;
private static final String OVERLAY2 = OVERLAY + "2";
private static final String TARGET2 = TARGET + "2";
+ private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
private static final int USER2 = USER + 1;
private static final String OVERLAY3 = OVERLAY + "3";
+ private static final OverlayIdentifier IDENTIFIER3 = new OverlayIdentifier(OVERLAY3);
private static final int USER3 = USER2 + 1;
private static final String CONFIG_SIGNATURE_REFERENCE_PKG = "com.test.ref";
@@ -60,10 +65,10 @@
@Test
public void testGetOverlayInfo() throws Exception {
- installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
- final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
assertNotNull(oi);
assertEquals(oi.packageName, OVERLAY);
assertEquals(oi.targetPackageName, TARGET);
@@ -72,19 +77,19 @@
@Test
public void testGetOverlayInfosForTarget() throws Exception {
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- installNewPackage(overlay(OVERLAY2, TARGET), USER);
- installNewPackage(overlay(OVERLAY3, TARGET), USER2);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY2, TARGET), USER);
+ installPackage(overlay(OVERLAY3, TARGET), USER2);
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
assertEquals(ois.size(), 2);
- assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
- assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+ assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+ assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
final List<OverlayInfo> ois2 = impl.getOverlayInfosForTarget(TARGET, USER2);
assertEquals(ois2.size(), 1);
- assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER2)));
+ assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER2)));
final List<OverlayInfo> ois3 = impl.getOverlayInfosForTarget(TARGET, USER3);
assertNotNull(ois3);
@@ -97,10 +102,10 @@
@Test
public void testGetOverlayInfosForUser() throws Exception {
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- installNewPackage(overlay(OVERLAY2, TARGET), USER);
- installNewPackage(overlay(OVERLAY3, TARGET2), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY2, TARGET), USER);
+ installPackage(overlay(OVERLAY3, TARGET2), USER);
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -109,13 +114,13 @@
final List<OverlayInfo> ois = everything.get(TARGET);
assertNotNull(ois);
assertEquals(ois.size(), 2);
- assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
- assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+ assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+ assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
final List<OverlayInfo> ois2 = everything.get(TARGET2);
assertNotNull(ois2);
assertEquals(ois2.size(), 1);
- assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER)));
+ assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER)));
final Map<String, List<OverlayInfo>> everything2 = impl.getOverlaysForUser(USER2);
assertNotNull(everything2);
@@ -124,26 +129,26 @@
@Test
public void testPriority() throws Exception {
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- installNewPackage(overlay(OVERLAY2, TARGET), USER);
- installNewPackage(overlay(OVERLAY3, TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ installPackage(overlay(OVERLAY2, TARGET), USER);
+ installPackage(overlay(OVERLAY3, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
- final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
- final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
+ final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
+ final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
+ final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER3, USER);
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
- assertEquals(impl.setLowestPriority(OVERLAY3, USER),
+ assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
- assertEquals(impl.setHighestPriority(OVERLAY3, USER),
- Optional.of(new PackageAndUser(TARGET, USER)));
+ assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
+ Set.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
- assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER),
+ assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
Optional.of(new PackageAndUser(TARGET, USER)));
assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@@ -151,61 +156,63 @@
@Test
public void testOverlayInfoStateTransitions() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
- assertNull(impl.getOverlayInfo(OVERLAY, USER));
+ assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
final FakeDeviceState.PackageBuilder target = target(TARGET);
- installNewPackage(target, USER);
- assertState(STATE_DISABLED, OVERLAY, USER);
+ installPackage(target, USER);
+ assertState(STATE_DISABLED, IDENTIFIER, USER);
- assertEquals(impl.setEnabled(OVERLAY, true, USER),
- Optional.of(new PackageAndUser(TARGET, USER)));
- assertState(STATE_ENABLED, OVERLAY, USER);
+ assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+ Set.of(new PackageAndUser(TARGET, USER)));
+ assertState(STATE_ENABLED, IDENTIFIER, USER);
// target upgrades do not change the state of the overlay
upgradePackage(target, USER);
- assertState(STATE_ENABLED, OVERLAY, USER);
+ assertState(STATE_ENABLED, IDENTIFIER, USER);
uninstallPackage(TARGET, USER);
- assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+ assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
- installNewPackage(target, USER);
- assertState(STATE_ENABLED, OVERLAY, USER);
+ installPackage(target, USER);
+ assertState(STATE_ENABLED, IDENTIFIER, USER);
}
@Test
public void testOnOverlayPackageUpgraded() throws Exception {
final FakeDeviceState.PackageBuilder target = target(TARGET);
final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
- installNewPackage(target, USER);
- installNewPackage(overlay, USER);
+ installPackage(target, USER);
+ installPackage(overlay, USER);
upgradePackage(overlay, USER);
// upgrade to a version where the overlay has changed its target
final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
- final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair =
- upgradePackage(overlay2, USER);
- assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER)));
- assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER)));
+ final Pair<Set<PackageAndUser>, Set<PackageAndUser>> pair = upgradePackage(overlay2, USER);
+ assertEquals(pair.first, Set.of(new PackageAndUser(TARGET, USER)));
+ assertEquals(
+ Set.of(new PackageAndUser(TARGET, USER),
+ new PackageAndUser("some.other.target", USER)),
+ pair.second);
}
@Test
public void testSetEnabledAtVariousConditions() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
- () -> impl.setEnabled(OVERLAY, true, USER));
+ () -> impl.setEnabled(IDENTIFIER, true, USER));
// request succeeded, and there was a change that needs to be
// propagated to the rest of the system
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET), USER);
- assertEquals(impl.setEnabled(OVERLAY, true, USER),
- Optional.of(new PackageAndUser(TARGET, USER)));
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET), USER);
+ assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+ Set.of(new PackageAndUser(TARGET, USER)));
// request succeeded, but nothing changed
- assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent());
+ assertTrue(impl.setEnabled(IDENTIFIER, true, USER).isEmpty());
}
@Test
@@ -214,8 +221,8 @@
reinitializeImpl();
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -232,8 +239,8 @@
reinitializeImpl();
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -247,8 +254,8 @@
@Test
public void testConfigSignaturePolicyNoConfig() throws Exception {
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -261,8 +268,8 @@
@Test
public void testConfigSignaturePolicyNoRefPkg() throws Exception {
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -279,8 +286,8 @@
reinitializeImpl();
addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
- installNewPackage(target(TARGET), USER);
- installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+ installPackage(target(TARGET), USER);
+ installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 2c477c8..16e0329 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,11 +24,12 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayInfo.State;
import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,17 +38,19 @@
import androidx.annotation.Nullable;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import org.junit.Assert;
import org.junit.Before;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import java.util.Set;
/** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
class OverlayManagerServiceImplTestsBase {
@@ -94,12 +97,11 @@
mConfigSignaturePackageName = packageName;
}
- void assertState(@State int expected, final String overlayPackageName, int userId) {
- final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
+ void assertState(@State int expected, final OverlayIdentifier overlay, int userId) {
+ final OverlayInfo info = mImpl.getOverlayInfo(overlay, userId);
if (info == null) {
- throw new IllegalStateException("package not installed");
+ throw new IllegalStateException("overlay '" + overlay + "' not installed");
}
-
final String msg = String.format("expected %s but was %s:",
OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
assertEquals(msg, expected, info.state);
@@ -152,17 +154,13 @@
*
* @throws IllegalStateException if the package is currently installed
*/
- void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId)
+ Set<PackageAndUser> installPackage(FakeDeviceState.PackageBuilder pkg, int userId)
throws OperationFailedException {
if (mState.select(pkg.packageName, userId) != null) {
throw new IllegalStateException("package " + pkg.packageName + " already installed");
}
mState.add(pkg, userId);
- if (pkg.targetPackage == null) {
- mImpl.onTargetPackageAdded(pkg.packageName, userId);
- } else {
- mImpl.onOverlayPackageAdded(pkg.packageName, userId);
- }
+ return CollectionUtils.emptyIfNull(mImpl.onPackageAdded(pkg.packageName, userId));
}
/**
@@ -178,26 +176,21 @@
*
* @throws IllegalStateException if the package is not currently installed
*/
- Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage(
+ Pair<Set<PackageAndUser>, Set<PackageAndUser>> upgradePackage(
FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException {
final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
if (replacedPackage == null) {
throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
- Optional<PackageAndUser> opt1 = Optional.empty();
- if (replacedPackage.targetPackageName != null) {
- opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
- }
+
+ final Set<PackageAndUser> updatedPackages1 =
+ CollectionUtils.emptyIfNull(mImpl.onPackageReplacing(pkg.packageName, userId));
mState.add(pkg, userId);
- Optional<PackageAndUser> opt2;
- if (pkg.targetPackage == null) {
- opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId);
- } else {
- opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
- }
+ final Set<PackageAndUser> updatedPackages2 =
+ CollectionUtils.emptyIfNull(mImpl.onPackageReplaced(pkg.packageName, userId));
- return Pair.create(opt1, opt2);
+ return Pair.create(updatedPackages1, updatedPackages2);
}
/**
@@ -208,17 +201,13 @@
*
* @throws IllegalStateException if the package is not currently installed
*/
- void uninstallPackage(String packageName, int userId) throws OperationFailedException {
+ Set<PackageAndUser> uninstallPackage(String packageName, int userId) {
final FakeDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null) {
throw new IllegalStateException("package " + packageName+ " not installed");
}
mState.remove(pkg.packageName);
- if (pkg.targetPackageName == null) {
- mImpl.onTargetPackageRemoved(pkg.packageName, userId);
- } else {
- mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
- }
+ return CollectionUtils.emptyIfNull(mImpl.onPackageRemoved(packageName, userId));
}
/** Represents the state of packages installed on a fake device. */
@@ -247,11 +236,6 @@
}
}
- List<Package> select(int userId) {
- return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
- .collect(Collectors.toList());
- }
-
Package select(String packageName, int userId) {
final Package pkg = mPackages.get(packageName);
return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
@@ -335,6 +319,21 @@
this.apkPath = apkPath;
this.certificate = certificate;
}
+
+ @Nullable
+ private AndroidPackage getPackageForUser(int user) {
+ if (!installedUserIds.contains(user)) {
+ return null;
+ }
+ final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
+ when(pkg.getPackageName()).thenReturn(packageName);
+ when(pkg.getBaseApkPath()).thenReturn(apkPath);
+ when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
+ when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
+ when(pkg.getOverlayTargetName()).thenReturn(targetOverlayableName);
+ when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
+ return pkg;
+ }
}
}
@@ -345,21 +344,29 @@
mState = state;
}
+ @NonNull
@Override
- public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
- final FakeDeviceState.Package pkg = mState.select(packageName, userId);
- if (pkg == null) {
- return null;
- }
- final ApplicationInfo ai = new ApplicationInfo();
- ai.sourceDir = pkg.apkPath;
- PackageInfo pi = new PackageInfo();
- pi.applicationInfo = ai;
- pi.packageName = pkg.packageName;
- pi.overlayTarget = pkg.targetPackageName;
- pi.targetOverlayableName = pkg.targetOverlayableName;
- pi.overlayCategory = "Fake-category-" + pkg.targetPackageName;
- return pi;
+ public ArrayMap<String, AndroidPackage> initializeForUser(int userId) {
+ final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
+ mState.mPackages.forEach((key, value) -> {
+ final AndroidPackage pkg = value.getPackageForUser(userId);
+ if (pkg != null) {
+ packages.put(key, pkg);
+ }
+ });
+ return packages;
+ }
+
+ @Nullable
+ @Override
+ public AndroidPackage getPackageForUser(@NonNull String packageName, int userId) {
+ final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
+ return pkgState == null ? null : pkgState.getPackageForUser(userId);
+ }
+
+ @Override
+ public boolean isInstantApp(@NonNull String packageName, int userId) {
+ return false;
}
@Override
@@ -371,14 +378,6 @@
}
@Override
- public List<PackageInfo> getOverlayPackages(int userId) {
- return mState.select(userId).stream()
- .filter(p -> p.targetPackageName != null)
- .map(p -> getPackageInfo(p.packageName, userId))
- .collect(Collectors.toList());
- }
-
- @Override
public @NonNull String getConfigSignaturePackage() {
return mConfigSignaturePackageName;
}
@@ -421,6 +420,9 @@
static class FakeIdmapDaemon extends IdmapDaemon {
private final FakeDeviceState mState;
private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
+ private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
+ new ArrayMap<>();
+ private int mFabricatedAssetSeq = 0;
FakeIdmapDaemon(FakeDeviceState state) {
this.mState = state;
@@ -433,10 +435,10 @@
}
@Override
- String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
- int userId) {
+ String createIdmap(String targetPath, String overlayPath, String overlayName,
+ int policies, boolean enforce, int userId) {
mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
- getCrc(overlayPath), targetPath, policies, enforce));
+ getCrc(overlayPath), targetPath, overlayName, policies, enforce));
return overlayPath;
}
@@ -446,8 +448,8 @@
}
@Override
- boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
- int userId) {
+ boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
+ boolean enforce, int userId) {
final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
if (idmap == null) {
return false;
@@ -461,6 +463,29 @@
return mIdmapFiles.containsKey(overlayPath);
}
+ @Override
+ FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+ final String path = Integer.toString(mFabricatedAssetSeq++);
+ final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
+ info.path = path;
+ info.overlayName = overlay.overlayName;
+ info.packageName = overlay.packageName;
+ info.targetPackageName = overlay.targetPackageName;
+ info.targetOverlayable = overlay.targetOverlayable;
+ mFabricatedOverlays.put(path, info);
+ return info;
+ }
+
+ @Override
+ boolean deleteFabricatedOverlay(@NonNull String path) {
+ return mFabricatedOverlays.remove(path) != null;
+ }
+
+ @Override
+ List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+ return new ArrayList<>(mFabricatedOverlays.values());
+ }
+
IdmapHeader getIdmap(String overlayPath) {
return mIdmapFiles.get(overlayPath);
}
@@ -469,14 +494,16 @@
private final int targetCrc;
private final int overlayCrc;
final String targetPath;
+ final String overlayName;
final int policies;
final boolean enforceOverlayable;
- private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
- boolean enforceOverlayable) {
+ private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
+ String overlayName, int policies, boolean enforceOverlayable) {
this.targetCrc = targetCrc;
this.overlayCrc = overlayCrc;
this.targetPath = targetPath;
+ this.overlayName = overlayName;
this.policies = policies;
this.enforceOverlayable = enforceOverlayable;
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index e3e7768..0a26f27 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -19,17 +19,20 @@
import static android.content.om.OverlayInfo.STATE_DISABLED;
import static android.content.om.OverlayInfo.STATE_ENABLED;
+import static org.junit.Assert.assertArrayEquals;
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.junit.Assert.fail;
+import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.text.TextUtils;
import android.util.TypedXmlPullParser;
import android.util.Xml;
+import androidx.annotation.NonNull;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -43,67 +46,32 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.IntStream;
-import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
public class OverlayManagerSettingsTests {
private OverlayManagerSettings mSettings;
+ private static int USER_0 = 0;
+ private static int USER_1 = 1;
- private static final OverlayInfo OVERLAY_A0 = new OverlayInfo(
- "com.test.overlay_a",
- "com.test.target",
- null,
- "some-category",
- "/data/app/com.test.overlay_a-1/base.apk",
- STATE_DISABLED,
- 0,
- 0,
- true);
+ private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
+ null /* overlayName */);
+ private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b",
+ null /* overlayName */);
+ private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c",
+ null /* overlayName */);
- private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
- "com.test.overlay_b",
- "com.test.target",
- null,
- "some-category",
- "/data/app/com.test.overlay_b-1/base.apk",
- STATE_DISABLED,
- 0,
- 0,
- true);
+ private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0);
+ private static final OverlayInfo OVERLAY_B_USER0 = createInfo(OVERLAY_B, USER_0);
+ private static final OverlayInfo OVERLAY_C_USER0 = createInfo(OVERLAY_C, USER_0);
- private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
- "com.test.overlay_c",
- "com.test.target",
- null,
- "some-category",
- "/data/app/com.test.overlay_c-1/base.apk",
- STATE_DISABLED,
- 0,
- 0,
- true);
+ private static final OverlayInfo OVERLAY_A_USER1 = createInfo(OVERLAY_A, USER_1);
+ private static final OverlayInfo OVERLAY_B_USER1 = createInfo(OVERLAY_B, USER_1);
- private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
- "com.test.overlay_a",
- "com.test.target",
- null,
- "some-category",
- "/data/app/com.test.overlay_a-1/base.apk",
- STATE_DISABLED,
- 1,
- 0,
- true);
-
- private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
- "com.test.overlay_b",
- "com.test.target",
- null,
- "some-category",
- "/data/app/com.test.overlay_b-1/base.apk",
- STATE_DISABLED,
- 1,
- 0,
- true);
+ private static final String TARGET_PACKAGE = "com.test.target";
@Before
public void setUp() throws Exception {
@@ -114,124 +82,112 @@
@Test
public void testSettingsInitiallyEmpty() throws Exception {
- final int userId = 0;
- Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(userId);
+ final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(0 /* userId */);
assertEquals(0, map.size());
}
@Test
public void testBasicSetAndGet() throws Exception {
- assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+ assertDoesNotContain(mSettings, OVERLAY_A_USER0);
- insert(OVERLAY_A0);
- assertContains(mSettings, OVERLAY_A0);
- OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
- assertEquals(OVERLAY_A0, oi);
+ insertSetting(OVERLAY_A_USER0);
+ assertContains(mSettings, OVERLAY_A_USER0);
+ final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A, USER_0);
+ assertEquals(OVERLAY_A_USER0, oi);
- assertTrue(mSettings.remove(OVERLAY_A0.packageName, OVERLAY_A0.userId));
- assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+ assertTrue(mSettings.remove(OVERLAY_A, USER_0));
+ assertDoesNotContain(mSettings, OVERLAY_A, USER_0);
}
@Test
public void testGetUsers() throws Exception {
- int[] users = mSettings.getUsers();
- assertEquals(0, users.length);
+ assertArrayEquals(new int[]{}, mSettings.getUsers());
- insert(OVERLAY_A0);
- users = mSettings.getUsers();
- assertEquals(1, users.length);
- assertContains(users, OVERLAY_A0.userId);
+ insertSetting(OVERLAY_A_USER0);
+ assertArrayEquals(new int[]{USER_0}, mSettings.getUsers());
- insert(OVERLAY_A1);
- insert(OVERLAY_B1);
- users = mSettings.getUsers();
- assertEquals(2, users.length);
- assertContains(users, OVERLAY_A0.userId);
- assertContains(users, OVERLAY_A1.userId);
+ insertSetting(OVERLAY_A_USER1);
+ insertSetting(OVERLAY_B_USER1);
+ assertArrayEquals(new int[]{USER_0, USER_1}, mSettings.getUsers());
}
@Test
public void testGetOverlaysForUser() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_A1);
- insert(OVERLAY_B1);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_A_USER1);
+ insertSetting(OVERLAY_B_USER0);
- Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(OVERLAY_A0.userId);
- assertEquals(1, map.keySet().size());
- assertTrue(map.keySet().contains(OVERLAY_A0.targetPackageName));
+ final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(USER_0);
+ assertEquals(Set.of(TARGET_PACKAGE), map.keySet());
- List<OverlayInfo> list = map.get(OVERLAY_A0.targetPackageName);
- assertEquals(2, list.size());
- assertTrue(list.contains(OVERLAY_A0));
- assertTrue(list.contains(OVERLAY_B0));
+ // Two overlays in user 0 target the same package
+ final List<OverlayInfo> list = map.get(TARGET_PACKAGE);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), list);
- // getOverlaysForUser should never return null
- map = mSettings.getOverlaysForUser(-1);
- assertNotNull(map);
- assertEquals(0, map.size());
+ // No users installed for user 3
+ assertEquals(Map.<String, List<OverlayInfo>>of(), mSettings.getOverlaysForUser(3));
}
@Test
public void testRemoveUser() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_A1);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_A_USER1);
- assertContains(mSettings, OVERLAY_A0);
- assertContains(mSettings, OVERLAY_B0);
- assertContains(mSettings, OVERLAY_A1);
+ assertContains(mSettings, OVERLAY_A_USER0);
+ assertContains(mSettings, OVERLAY_B_USER0);
+ assertContains(mSettings, OVERLAY_A_USER1);
- mSettings.removeUser(OVERLAY_A0.userId);
+ mSettings.removeUser(USER_0);
- assertDoesNotContain(mSettings, OVERLAY_A0);
- assertDoesNotContain(mSettings, OVERLAY_B0);
- assertContains(mSettings, OVERLAY_A1);
+ assertDoesNotContain(mSettings, OVERLAY_A_USER0);
+ assertDoesNotContain(mSettings, OVERLAY_B_USER0);
+ assertContains(mSettings, OVERLAY_A_USER1);
}
@Test
public void testOrderOfNewlyAddedItems() throws Exception {
// new items are appended to the list
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_C0);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_C_USER0);
- List<OverlayInfo> list =
- mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
// overlays keep their positions when updated
- mSettings.setState(OVERLAY_B0.packageName, OVERLAY_B0.userId, STATE_ENABLED);
- OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+ mSettings.setState(OVERLAY_B, USER_0, STATE_ENABLED);
+ final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B, USER_0);
+ assertNotNull(oi);
- list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, oi, OVERLAY_C0);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, oi, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
}
@Test
public void testSetPriority() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_C0);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_C_USER0);
- List<OverlayInfo> list =
- mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
- boolean changed = mSettings.setPriority(OVERLAY_B0.packageName, OVERLAY_C0.packageName,
- OVERLAY_B0.userId);
- assertTrue(changed);
- list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+ assertTrue(mSettings.setPriority(OVERLAY_B, OVERLAY_C, USER_0));
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
- changed =
- mSettings.setPriority(OVERLAY_B0.packageName, "does.not.exist", OVERLAY_B0.userId);
- assertFalse(changed);
- list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+ // Nothing happens if the parent package cannot be found
+ assertFalse(mSettings.setPriority(OVERLAY_B, new OverlayIdentifier("does.not.exist"),
+ USER_0));
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
- OverlayInfo otherTarget = new OverlayInfo(
+ // An overlay should not affect the priority of overlays targeting a different package
+ final OverlayInfo otherTarget = new OverlayInfo(
"com.test.overlay_other",
+ null,
"com.test.some.other.target",
null,
"some-category",
@@ -239,45 +195,36 @@
STATE_DISABLED,
0,
0,
- true);
- insert(otherTarget);
- changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
- OVERLAY_A0.userId);
- assertFalse(changed);
+ true,
+ false);
+ insertSetting(otherTarget);
+ assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
}
@Test
public void testSetLowestPriority() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_C0);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_C_USER0);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
- List<OverlayInfo> list =
- mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
- boolean changed = mSettings.setLowestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
- assertTrue(changed);
-
- list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_B0, OVERLAY_A0, OVERLAY_C0);
+ assertTrue(mSettings.setLowestPriority(OVERLAY_B, USER_0));
+ assertListsAreEqual(List.of(OVERLAY_B_USER0, OVERLAY_A_USER0, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
}
@Test
public void testSetHighestPriority() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
- insert(OVERLAY_C0);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
+ insertSetting(OVERLAY_C_USER0);
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+ mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
- List<OverlayInfo> list =
- mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
- boolean changed = mSettings.setHighestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
- assertTrue(changed);
-
- list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
- assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+ assertTrue(mSettings.setHighestPriority(OVERLAY_B, USER_0));
+ assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+ mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0));
}
// tests: persist and restore
@@ -294,8 +241,8 @@
@Test
public void testPersistDifferentOverlaysSameUser() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B0);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER0);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -304,17 +251,17 @@
assertEquals(1, countXmlTags(xml, "overlays"));
assertEquals(2, countXmlTags(xml, "item"));
assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
- OVERLAY_A0.packageName));
+ OVERLAY_A.getPackageName()));
assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
- OVERLAY_B0.packageName));
+ OVERLAY_B.getPackageName()));
assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
- Integer.toString(OVERLAY_A0.userId)));
+ Integer.toString(USER_0)));
}
@Test
public void testPersistSameOverlayDifferentUsers() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_A1);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_A_USER1);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -323,17 +270,17 @@
assertEquals(1, countXmlTags(xml, "overlays"));
assertEquals(2, countXmlTags(xml, "item"));
assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
- OVERLAY_A0.packageName));
+ OVERLAY_A.getPackageName()));
assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
- Integer.toString(OVERLAY_A0.userId)));
+ Integer.toString(USER_0)));
assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
- Integer.toString(OVERLAY_A1.userId)));
+ Integer.toString(USER_1)));
}
@Test
public void testPersistEnabled() throws Exception {
- insert(OVERLAY_A0);
- mSettings.setEnabled(OVERLAY_A0.packageName, OVERLAY_A0.userId, true);
+ insertSetting(OVERLAY_A_USER0);
+ mSettings.setEnabled(OVERLAY_A, USER_0, true);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -351,7 +298,7 @@
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
mSettings.restore(is);
- assertDoesNotContain(mSettings, "com.test.overlay", 0);
+ assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0);
}
@Test
@@ -361,6 +308,7 @@
"<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
+ "<overlays version='" + version + "'>\n"
+ "<item packageName='com.test.overlay'\n"
+ + " overlayName='test'\n"
+ " userId='1234'\n"
+ " targetPackageName='com.test.target'\n"
+ " baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
@@ -373,20 +321,22 @@
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
mSettings.restore(is);
- OverlayInfo oi = mSettings.getOverlayInfo("com.test.overlay", 1234);
+ final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
+ OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
assertNotNull(oi);
assertEquals("com.test.overlay", oi.packageName);
+ assertEquals("test", oi.overlayName);
assertEquals("com.test.target", oi.targetPackageName);
assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
assertEquals(1234, oi.userId);
assertEquals(STATE_DISABLED, oi.state);
- assertFalse(mSettings.getEnabled("com.test.overlay", 1234));
+ assertFalse(mSettings.getEnabled(identifier, 1234));
}
@Test
public void testPersistAndRestore() throws Exception {
- insert(OVERLAY_A0);
- insert(OVERLAY_B1);
+ insertSetting(OVERLAY_A_USER0);
+ insertSetting(OVERLAY_B_USER1);
ByteArrayOutputStream os = new ByteArrayOutputStream();
mSettings.persist(os);
@@ -394,11 +344,11 @@
OverlayManagerSettings newSettings = new OverlayManagerSettings();
newSettings.restore(is);
- OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
- assertEquals(OVERLAY_A0, a);
+ OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0);
+ assertEquals(OVERLAY_A_USER0, a);
- OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B1.packageName, OVERLAY_B1.userId);
- assertEquals(OVERLAY_B1, b);
+ OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1);
+ assertEquals(OVERLAY_B_USER1, b);
}
private int countXmlTags(InputStream in, String tagToLookFor) throws Exception {
@@ -433,43 +383,53 @@
return count;
}
- private void insert(OverlayInfo oi) throws Exception {
- mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
- true, false,0, oi.category);
- mSettings.setState(oi.packageName, oi.userId, oi.state);
- mSettings.setEnabled(oi.packageName, oi.userId, false);
+ private void insertSetting(OverlayInfo oi) throws Exception {
+ mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
+ oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
+ mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
+ mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
}
private static void assertContains(final OverlayManagerSettings settings,
final OverlayInfo oi) {
- assertContains(settings, oi.packageName, oi.userId);
- }
-
- private static void assertContains(final OverlayManagerSettings settings,
- final String packageName, int userId) {
try {
- settings.getOverlayInfo(packageName, userId);
+ settings.getOverlayInfo(oi.getOverlayIdentifier(), oi.userId);
} catch (OverlayManagerSettings.BadKeyException e) {
- fail(String.format("settings does not contain packageName=%s userId=%d",
- packageName, userId));
+ fail(String.format("settings does not contain overlay=%s userId=%d",
+ oi.getOverlayIdentifier(), oi.userId));
}
}
private static void assertDoesNotContain(final OverlayManagerSettings settings,
final OverlayInfo oi) {
- assertDoesNotContain(settings, oi.packageName, oi.userId);
+ assertDoesNotContain(settings, oi.getOverlayIdentifier(), oi.userId);
}
private static void assertDoesNotContain(final OverlayManagerSettings settings,
- final String packageName, int userId) {
+ final OverlayIdentifier overlay, int userId) {
try {
- settings.getOverlayInfo(packageName, userId);
- fail(String.format("settings contains packageName=%s userId=%d", packageName, userId));
+ settings.getOverlayInfo(overlay, userId);
+ fail(String.format("settings contains overlay=%s userId=%d", overlay, userId));
} catch (OverlayManagerSettings.BadKeyException e) {
// do nothing: we expect to end up here
}
}
+ private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) {
+ return new OverlayInfo(
+ identifier.getPackageName(),
+ identifier.getOverlayName(),
+ "com.test.target",
+ null,
+ "some-category",
+ "/data/app/" + identifier + "/base.apk",
+ STATE_DISABLED,
+ userId,
+ 0,
+ true,
+ false);
+ }
+
private static void assertContains(int[] haystack, int needle) {
List<Integer> list = IntStream.of(haystack)
.boxed()
@@ -490,16 +450,11 @@
}
}
- private static void assertListsAreEqual(List<OverlayInfo> list, OverlayInfo... array) {
- List<OverlayInfo> other = Stream.of(array)
- .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
- assertListsAreEqual(list, other);
- }
-
- private static void assertListsAreEqual(List<OverlayInfo> list, List<OverlayInfo> other) {
- if (!list.equals(other)) {
+ private static void assertListsAreEqual(
+ @NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) {
+ if (!expected.equals(actual)) {
fail(String.format("lists [%s] and [%s] differ",
- TextUtils.join(",", list), TextUtils.join(",", other)));
+ TextUtils.join(",", expected), TextUtils.join(",", actual)));
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
deleted file mode 100644
index 79935c2..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-
-import com.android.internal.os.BackgroundThread;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.function.Predicate;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-@Presubmit
-@RunWith(JUnit4.class)
-public class StagingManagerTest {
- @Rule
- public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
- private File mTmpDir;
- private StagingManager mStagingManager;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
- StorageManager storageManager = Mockito.mock(StorageManager.class);
- Context context = Mockito.mock(Context.class);
- when(storageManager.isCheckpointSupported()).thenReturn(true);
- when(context.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
- when(context.getSystemService(eq(Context.STORAGE_SERVICE))).thenReturn(storageManager);
-
- mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
- mStagingManager = new StagingManager(context, null);
- }
-
- /**
- * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
- * check.
- */
- @Test
- public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
- throws Exception {
- // Create 2 sessions with overlapping packages
- StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
- StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
-
- mStagingManager.createSession(session1);
- mStagingManager.createSession(session2);
- // Session1 should not fail in spite of the overlapping packages
- mStagingManager.checkNonOverlappingWithStagedSessions(session1);
- // Session2 should fail due to overlapping packages
- assertThrows(PackageManagerException.class,
- () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
- }
-
- private StagingManager.StagedSession createSession(int sessionId, String packageName,
- long committedMillis) {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.isStaged = true;
-
- InstallSource installSource = InstallSource.create("testInstallInitiator",
- "testInstallOriginator", "testInstaller", "testAttributionTag");
-
- PackageInstallerSession session = new PackageInstallerSession(
- /* callback */ null,
- /* context */ null,
- /* pm */ null,
- /* sessionProvider */ null,
- /* looper */ BackgroundThread.getHandler().getLooper(),
- /* stagingManager */ null,
- /* sessionId */ sessionId,
- /* userId */ 456,
- /* installerUid */ -1,
- /* installSource */ installSource,
- /* sessionParams */ params,
- /* createdMillis */ 0L,
- /* committedMillis */ committedMillis,
- /* stageDir */ mTmpDir,
- /* stageCid */ null,
- /* files */ null,
- /* checksums */ null,
- /* prepared */ true,
- /* committed */ true,
- /* destroyed */ false,
- /* sealed */ false, // Setting to true would trigger some PM logic.
- /* childSessionIds */ null,
- /* parentSessionId */ -1,
- /* isReady */ false,
- /* isFailed */ false,
- /* isApplied */false,
- /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
- /* stagedSessionErrorMessage */ "no error");
-
- StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
- doReturn(packageName).when(stagedSession).getPackageName();
- doAnswer(invocation -> {
- Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
- return filter.test(stagedSession);
- }).when(stagedSession).sessionContains(any());
- return stagedSession;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
int[] reasons = new int[] {
PackageManagerService.REASON_FIRST_BOOT,
- PackageManagerService.REASON_BOOT,
+ PackageManagerService.REASON_POST_BOOT,
PackageManagerService.REASON_INSTALL,
PackageManagerService.REASON_BACKGROUND_DEXOPT,
PackageManagerService.REASON_AB_OTA,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index b28994c..5574836 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 6dcfb42..daa1b25 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -27,7 +27,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
new file mode 100644
index 0000000..aa87958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ec28baf..07475e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1686,6 +1686,11 @@
}
@Override
+ protected void ensureFilters(ServiceInfo si, int userId) {
+
+ }
+
+ @Override
protected void loadDefaultsFromConfig() {
mDefaultComponents.addAll(mDefaults);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index afcf08ef..80a046a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,11 @@
*/
package com.android.server.notification;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -27,11 +32,14 @@
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
import android.service.notification.NotificationListenerFilter;
+import android.service.notification.NotificationListenerService;
import android.util.ArraySet;
import android.util.Pair;
+import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
@@ -47,8 +55,6 @@
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
public class NotificationListenersTest extends UiServiceTestCase {
@@ -80,22 +86,21 @@
@Test
public void testReadExtraTag() throws Exception {
- String xml = "<req_listeners>"
+ String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+ "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
+ "<allowed types=\"7\" />"
- + "<disallowed pkgs=\"\" />"
+ "</listener>"
+ "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">"
+ "<allowed types=\"4\" />"
- + "<disallowed pkgs=\"something\" />"
+ + "<disallowed pkg=\"pkg1\" uid=\"243\"/>"
+ "</listener>"
- + "</req_listeners>";
+ + "</" + TAG_REQUESTED_LISTENERS + ">";
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mListeners.readExtraTag("req_listeners", parser);
+ mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser);
validateListenersFromXml();
}
@@ -103,8 +108,9 @@
@Test
public void testWriteExtraTag() throws Exception {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -134,16 +140,18 @@
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
.isEqualTo(4);
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10))
.getDisallowedPackages())
- .contains("something");
+ .contains(a1);
}
@Test
public void testOnUserRemoved() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -155,58 +163,68 @@
}
@Test
- public void testOnUserUnlocked() {
- // one exists already, say from xml
- NotificationListenerFilter nlf =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
- mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
-
- // new service exists or backfilling on upgrade to S
+ public void testEnsureFilters_newServiceNoMetadata() {
ServiceInfo si = new ServiceInfo();
- si.permission = mListeners.getConfig().bindPermission;
+ si.packageName = "new2";
+ si.name = "comp2";
+
+ mListeners.ensureFilters(si, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull();
+ }
+
+ @Test
+ public void testEnsureFilters_preExisting() {
+ // one exists already, say from xml
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ NotificationListenerFilter nlf =
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
+ ServiceInfo siOld = new ServiceInfo();
+ siOld.packageName = mCn2.getPackageName();
+ siOld.name = mCn2.getClassName();
+
+ mListeners.ensureFilters(siOld, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf);
+ }
+
+ @Test
+ public void testEnsureFilters_newServiceWithMetadata() {
+ ServiceInfo si = new ServiceInfo();
si.packageName = "new";
si.name = "comp";
- ResolveInfo ri = new ResolveInfo();
- ri.serviceInfo = si;
+ si.metaData = new Bundle();
+ si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2");
- // incorrect service
- ServiceInfo si2 = new ServiceInfo();
- ResolveInfo ri2 = new ResolveInfo();
- ri2.serviceInfo = si2;
- si2.packageName = "new2";
- si2.name = "comp2";
-
- List<ResolveInfo> ris = new ArrayList<>();
- ris.add(ri);
- ris.add(ri2);
-
- when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
-
- mListeners.onUserUnlocked(0);
-
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
- .isEqualTo(4);
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
- .getDisallowedPackages())
- .contains("something");
+ mListeners.ensureFilters(si, 0);
assertThat(mListeners.getNotificationListenerFilter(
Pair.create(si.getComponentName(), 0)).getTypes())
- .isEqualTo(15);
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
- .getDisallowedPackages())
- .isEmpty();
+ .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING);
+ }
- assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
- .isNull();
+ @Test
+ public void testEnsureFilters_newServiceWithEmptyMetadata() {
+ ServiceInfo si = new ServiceInfo();
+ si.packageName = "new";
+ si.name = "comp";
+ si.metaData = new Bundle();
+ si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "");
+ mListeners.ensureFilters(si, 0);
+
+ assertThat(mListeners.getNotificationListenerFilter(
+ Pair.create(si.getComponentName(), 0)).getTypes())
+ .isEqualTo(0);
}
@Test
public void testOnPackageChanged() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
@@ -224,8 +242,9 @@
@Test
public void testOnPackageChanged_removing() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
@@ -239,4 +258,21 @@
.isEqualTo(4);
}
+ @Test
+ public void testOnPackageChanged_removingDisallowedPackage() {
+ NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+ VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+ NotificationListenerFilter nlf2 =
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
+ mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
+
+ String[] pkgs = new String[] {"pkg1"};
+ int[] uids = new int[] {243};
+ mListeners.onPackagesChanged(true, pkgs, uids);
+
+ assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
+ .getDisallowedPackages()).isEmpty();
+ }
+
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e8888f4..a640509 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -464,7 +464,7 @@
// Setup managed services
when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
- when(mNlf.isPackageAllowed(anyString())).thenReturn(true);
+ when(mNlf.isPackageAllowed(any())).thenReturn(true);
when(mNlf.isPackageAllowed(null)).thenReturn(true);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
@@ -7307,7 +7307,7 @@
@Test
public void testIsVisibleToListener_disallowedPackage() {
- when(mNlf.isPackageAllowed(null)).thenReturn(false);
+ when(mNlf.isPackageAllowed(any())).thenReturn(false);
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getUserId()).thenReturn(10);
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 b4fd302..781cfec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1834,7 +1834,7 @@
mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
}
- private void performLayout(DisplayContent dc) {
+ static void performLayout(DisplayContent dc) {
dc.setLayoutNeeded();
dc.performLayout(true /* initial */, false /* updateImeWindows */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 21fd04e..925b6f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -29,6 +29,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -66,6 +71,8 @@
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
+import android.view.Surface;
+import android.window.TaskSnapshot;
import androidx.test.filters.MediumTest;
@@ -446,12 +453,8 @@
doReturn(false).when(child2).isOrganized();
mRecentTasks.add(root);
- doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
- doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
- final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0 /* callingUid */).getList();
-
// Make sure only organized child will be appended.
+ final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos;
assertEquals(childrenTaskInfos.size(), 1);
assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId);
@@ -1051,9 +1054,6 @@
@Test
public void testTaskInfo_expectNoExtras() {
- doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
- doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-
final Bundle data = new Bundle();
data.putInt("key", 100);
final Task task1 = createTaskBuilder(".Task").build();
@@ -1063,8 +1063,7 @@
.build();
mRecentTasks.add(r1.getTask());
- final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+ final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
assertTrue(infos.size() == 1);
for (int i = 0; i < infos.size(); i++) {
final Bundle extras = infos.get(i).baseIntent.getExtras();
@@ -1072,6 +1071,60 @@
}
}
+ @Test
+ public void testLastSnapshotData_snapshotSaved() {
+ final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80));
+ final Task task1 = createTaskBuilder(".Task").build();
+ task1.onSnapshotChanged(snapshot);
+
+ mRecentTasks.add(task1);
+ final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+ final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+ infos.get(0).lastSnapshotData;
+ assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+ assertTrue(lastSnapshotData.bufferSize.equals(80, 80));
+ }
+
+ @Test
+ public void testLastSnapshotData_noBuffer() {
+ final Task task1 = createTaskBuilder(".Task").build();
+ final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null);
+ task1.onSnapshotChanged(snapshot);
+
+ mRecentTasks.add(task1);
+ final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+ final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+ infos.get(0).lastSnapshotData;
+ assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+ assertNull(lastSnapshotData.bufferSize);
+ }
+
+ @Test
+ public void testLastSnapshotData_notSet() {
+ final Task task1 = createTaskBuilder(".Task").build();
+
+ mRecentTasks.add(task1);
+ final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+ final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+ infos.get(0).lastSnapshotData;
+ assertNull(lastSnapshotData.taskSize);
+ assertNull(lastSnapshotData.bufferSize);
+ }
+
+ private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
+ HardwareBuffer buffer = null;
+ if (bufferSize != null) {
+ buffer = mock(HardwareBuffer.class);
+ doReturn(bufferSize.x).when(buffer).getWidth();
+ doReturn(bufferSize.y).when(buffer).getHeight();
+ }
+ return new TaskSnapshot(1, new ComponentName("", ""), buffer,
+ ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+ Surface.ROTATION_0, taskSize, new Rect() /* insets */, false /* isLowResolution */,
+ true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
+ false /* isTranslucent */, false /* hasImeSurface */);
+ }
+
/**
* Ensures that the raw recent tasks list is in the provided order. Note that the expected tasks
* should be ordered from least to most recent.
@@ -1084,15 +1137,19 @@
}
}
+ private List<RecentTaskInfo> getRecentTasks(int flags) {
+ doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
+ doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
+ return mRecentTasks.getRecentTasks(MAX_VALUE, flags, true /* getTasksAllowed */,
+ TEST_USER_0_ID, 0 /* callingUid */).getList();
+ }
+
/**
* Ensures that the recent tasks list is in the provided order. Note that the expected tasks
* should be ordered from least to most recent.
*/
private void assertGetRecentTasksOrder(int getRecentTaskFlags, Task... expectedTasks) {
- doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
- doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
- List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, getRecentTaskFlags,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+ List<RecentTaskInfo> infos = getRecentTasks(getRecentTaskFlags);
assertTrue(expectedTasks.length == infos.size());
for (int i = 0; i < infos.size(); i++) {
assertTrue(expectedTasks[i].mTaskId == infos.get(i).taskId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3231f8b..8969695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -67,11 +67,14 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.when;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
import android.view.InputWindowHandle;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -559,6 +562,46 @@
assertTrue(window.isVisibleByPolicy());
}
+ @Test
+ public void testCompatOverrideScale() {
+ final float overrideScale = 2; // 0.5x on client side.
+ final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
+ spyOn(cmp);
+ doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
+ final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
+ makeWindowVisible(w);
+ w.setRequestedSize(100, 200);
+ w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+ DisplayContentTests.performLayout(mDisplayContent);
+
+ // Frame on screen = 100x200. Compat frame on client = 50x100.
+ final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
+ unscaledCompatFrame.scale(overrideScale);
+ assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+
+ // Surface should apply the scale.
+ w.prepareSurfaces();
+ verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
+ overrideScale, 0, 0, overrideScale);
+
+ // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
+ final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
+ mContext.getApplicationInfo());
+ final Configuration winConfig = w.getConfiguration();
+ final Configuration clientConfig = new Configuration(w.getConfiguration());
+ compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
+
+ assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
+ assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);
+ assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp);
+ assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale));
+
+ final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds());
+ unscaledClientBounds.scale(overrideScale);
+ assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
+ }
+
@UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eb6c6ed..83b30a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -337,6 +337,7 @@
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
attrs.setTitle(name);
+ attrs.packageName = "test";
final WindowState w = new WindowState(service, session, iWindow, token, parent,
OP_NONE, attrs, VISIBLE, ownerId, userId,
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 8ee72b5..60172a3 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,6 +1,5 @@
badhri@google.com
elaurent@google.com
-moltmann@google.com
albertccwang@google.com
jameswei@google.com
howardyen@google.com
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
- PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+ PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
+ "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
+ " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 99f2e5e..c6757fb 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -646,7 +646,7 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED})
public @interface OverrideNetworkType {}
/**
diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java
deleted file mode 100644
index 7c7eb9f..0000000
--- a/telephony/java/android/telephony/RadioInterfaceCapabilities.java
+++ /dev/null
@@ -1,53 +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 android.telephony;
-
-import android.util.ArraySet;
-
-/**
- * Contains the set of supported capabilities that the Radio Interface supports on this device.
- *
- * @hide
- */
-public class RadioInterfaceCapabilities {
-
- private final ArraySet<String> mSupportedCapabilities;
-
-
- public RadioInterfaceCapabilities() {
- mSupportedCapabilities = new ArraySet<>();
- }
-
- /**
- * Marks a capability as supported
- *
- * @param capabilityName the name of the capability
- */
- public void addSupportedCapability(
- @TelephonyManager.RadioInterfaceCapability String capabilityName) {
- mSupportedCapabilities.add(capabilityName);
- }
-
- /**
- * Whether the capability is supported
- *
- * @param capabilityName the name of the capability
- */
- public boolean isSupported(String capabilityName) {
- return mSupportedCapabilities.contains(capabilityName);
- }
-}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 1fcb504..5b5570b 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -66,9 +66,26 @@
* {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
* capability or is currently connected to the secondary
* {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+ * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead.
*/
+ @Deprecated
public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+ /**
+ * Override network type when the device is connected NR cellular network and the data rate is
+ * higher than the generic 5G date rate.
+ * Including but not limited to
+ * <ul>
+ * <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
+ * <li>The device is connected to the specific network which the carrier is using
+ * proprietary means to provide a faster overall data connection than would be otherwise
+ * possible. This may include using other bands unique to the carrier, or carrier
+ * aggregation, for example.</li>
+ * </ul>
+ * One of the use case is that UX can show a different icon, for example, "5G+"
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+
@NetworkType
private final int mNetworkType;
@@ -169,7 +186,7 @@
case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
- case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
default: return "UNKNOWN";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 16ffd9e..ee3a0ef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8491,6 +8491,11 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param subId the id of the subscription to set the preferred network type for.
* @param networkType the preferred network type
@@ -8524,6 +8529,11 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param networkTypeBitmask The bitmask of preferred network types.
* @return true on success; false on any failure.
@@ -8550,6 +8560,11 @@
* Set the allowed network types of the device. This is for carrier or privileged apps to
* enable/disable certain network types on the device. The user preferred network types should
* be set through {@link #setPreferredNetworkTypeBitmask}.
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setPreferredNetworkTypesBitmap is used instead.
*
* @param allowedNetworkTypes The bitmask of allowed network types.
* @return true on success; false on any failure.
@@ -8624,6 +8639,11 @@
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
* including the configuration done through other reasons.
+ * <p>
+ * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) 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.
@@ -14861,10 +14881,24 @@
public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE =
"CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+ /**
+ * Indicates whether {@link #setPreferredNetworkType}, {@link
+ * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and
+ * {@link #setAllowedNetworkTypesForReason} rely on
+ * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio
+ * interface.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
+ "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
+ CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
})
public @interface RadioInterfaceCapability {}
diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
index b2bcfeb..ad5bbf2 100644
--- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
@@ -54,12 +54,26 @@
}
.build()
+ private val dataFromPasspoint = CaptivePortalData.Builder()
+ .setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+ .setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+ .setCaptive(true)
+ .apply {
+ if (SdkLevel.isAtLeastS()) {
+ setVenueFriendlyName("venue friendly name")
+ }
+ }
+ .build()
+
private fun makeBuilder() = CaptivePortalData.Builder(data)
@Test
fun testParcelUnparcel() {
- val fieldCount = if (SdkLevel.isAtLeastS()) 8 else 7
+ val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
assertParcelSane(data, fieldCount)
+ assertParcelSane(dataFromPasspoint, fieldCount)
assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
@@ -83,6 +97,27 @@
assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") }
assertNotEqualsAfterChange { it.setVenueFriendlyName(null) }
}
+
+ assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build())
+ assertNotEqualsAfterChange { it.setUserPortalUrl(
+ Uri.parse("https://tc.example.com/passpoint")) }
+ assertNotEqualsAfterChange { it.setUserPortalUrl(
+ Uri.parse("https://tc.example.com/passpoint"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+ assertNotEqualsAfterChange { it.setUserPortalUrl(
+ Uri.parse("https://tc.example.com/other"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+ assertNotEqualsAfterChange { it.setUserPortalUrl(
+ Uri.parse("https://tc.example.com/passpoint"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+ assertNotEqualsAfterChange { it.setVenueInfoUrl(
+ Uri.parse("https://venue.example.com/passpoint")) }
+ assertNotEqualsAfterChange { it.setVenueInfoUrl(
+ Uri.parse("https://venue.example.com/other"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+ assertNotEqualsAfterChange { it.setVenueInfoUrl(
+ Uri.parse("https://venue.example.com/passpoint"),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
}
@Test
@@ -130,6 +165,22 @@
assertEquals("venue friendly name", data.venueFriendlyName)
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ fun testGetVenueInfoUrlSource() {
+ assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+ data.venueInfoUrlSource)
+ assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+ dataFromPasspoint.venueInfoUrlSource)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ fun testGetUserPortalUrlSource() {
+ assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+ data.userPortalUrlSource)
+ assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+ dataFromPasspoint.userPortalUrlSource)
+ }
+
private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
CaptivePortalData.Builder(this).apply { mutator(this) }.build()
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e1fd8f0..e639a36 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -362,8 +362,15 @@
private static final String INTERFACE_NAME = "interface";
- private static final String TEST_VENUE_URL_NA = "https://android.com/";
+ private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/";
+ private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/";
+ private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT =
+ "https://android.com/terms/";
+ private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER =
+ "https://example.com/terms/";
private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/";
+ private static final String TEST_USER_PORTAL_API_URL_CAPPORT =
+ "https://android.com/user/api/capport/";
private static final String TEST_FRIENDLY_NAME = "Network friendly name";
private static final String TEST_REDIRECT_URL = "http://example.com/firstPath";
@@ -3289,39 +3296,68 @@
}
private class CaptivePortalTestData {
- CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData,
- CaptivePortalData expectedMergedData) {
- mNaData = naData;
+ CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData,
+ CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData,
+ CaptivePortalData expectedMergedOtherData) {
+ mNaPasspointData = naPasspointData;
mCapportData = capportData;
- mExpectedMergedData = expectedMergedData;
+ mNaOtherData = naOtherData;
+ mExpectedMergedPasspointData = expectedMergedPasspointData;
+ mExpectedMergedOtherData = expectedMergedOtherData;
}
- public final CaptivePortalData mNaData;
+ public final CaptivePortalData mNaPasspointData;
public final CaptivePortalData mCapportData;
- public final CaptivePortalData mExpectedMergedData;
+ public final CaptivePortalData mNaOtherData;
+ public final CaptivePortalData mExpectedMergedPasspointData;
+ public final CaptivePortalData mExpectedMergedOtherData;
+
}
private CaptivePortalTestData setupCaptivePortalData() {
final CaptivePortalData capportData = new CaptivePortalData.Builder()
.setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
.setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+ .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
.setExpiryTime(1000000L)
.setBytesRemaining(12345L)
.build();
- final CaptivePortalData naData = new CaptivePortalData.Builder()
+ final CaptivePortalData naPasspointData = new CaptivePortalData.Builder()
.setBytesRemaining(80802L)
- .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+ .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+ .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
.setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
- final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder()
+ final CaptivePortalData naOtherData = new CaptivePortalData.Builder()
+ .setBytesRemaining(80802L)
+ .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+ .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+ .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+
+ final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder()
.setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
.setBytesRemaining(12345L)
.setExpiryTime(1000000L)
- .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+ .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+ .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+ CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
.setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
- return new CaptivePortalTestData(naData, capportData, expectedMergedData);
+ final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder()
+ .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
+ .setBytesRemaining(12345L)
+ .setExpiryTime(1000000L)
+ .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+ .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
+ .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+ return new CaptivePortalTestData(naPasspointData, capportData, naOtherData,
+ expectedMergedPasspointData, expectedMergedOtherData);
}
@Test
@@ -3335,15 +3371,26 @@
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
- // Venue URL and friendly name from Network agent, confirm that API data gets precedence
- // on the bytes remaining.
+ // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm
+ // that API data gets precedence on the bytes remaining.
final LinkProperties linkProperties = new LinkProperties();
- linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+ linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
mWiFiNetworkAgent.sendLinkProperties(linkProperties);
// Make sure that the capport data is merged
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
- lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+ lp -> captivePortalTestData.mExpectedMergedPasspointData
+ .equals(lp.getCaptivePortalData()));
+
+ // Now send this information from non-Passpoint source, confirm that Capport data takes
+ // precedence
+ linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+ mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+ // Make sure that the capport data is merged
+ captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+ lp -> captivePortalTestData.mExpectedMergedOtherData
+ .equals(lp.getCaptivePortalData()));
// Create a new LP with no Network agent capport data
final LinkProperties newLps = new LinkProperties();
@@ -3360,12 +3407,12 @@
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> lp.getCaptivePortalData() == null);
- newLps.setCaptivePortalData(captivePortalTestData.mNaData);
+ newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
mWiFiNetworkAgent.sendLinkProperties(newLps);
// Make sure that only the network agent capport data is available
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
- lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+ lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
}
@Test
@@ -3376,12 +3423,12 @@
// Venue URL and friendly name from Network agent, confirm that API data gets precedence
// on the bytes remaining.
final LinkProperties linkProperties = new LinkProperties();
- linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+ linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
mWiFiNetworkAgent.sendLinkProperties(linkProperties);
// Make sure that the data is saved correctly
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
- lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+ lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
// Expected merged data: Network agent data is preferred, and values that are not used by
// it are merged from capport data
@@ -3389,7 +3436,8 @@
// Make sure that the Capport data is merged correctly
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
- lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+ lp -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+ lp.getCaptivePortalData()));
// Now set the naData to null
linkProperties.setCaptivePortalData(null);
@@ -3400,6 +3448,32 @@
lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
}
+ @Test
+ public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport()
+ throws Exception {
+ final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
+ final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
+
+ // Venue URL and friendly name from Network agent, confirm that API data gets precedence
+ // on the bytes remaining.
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+ mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+ // Make sure that the data is saved correctly
+ captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+ lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData()));
+
+ // Expected merged data: Network agent data is preferred, and values that are not used by
+ // it are merged from capport data
+ mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+
+ // Make sure that the Capport data is merged correctly
+ captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+ lp -> captivePortalTestData.mExpectedMergedOtherData.equals(
+ lp.getCaptivePortalData()));
+ }
+
private NetworkRequest.Builder newWifiRequestBuilder() {
return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
}
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 799bcc8..c86224a 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -33,6 +33,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.IpSecAlgorithm;
@@ -44,6 +45,7 @@
import android.net.IpSecTunnelInterfaceResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.Network;
import android.os.Binder;
import android.os.INetworkManagementService;
@@ -53,6 +55,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.IpSecService.TunnelInterfaceRecord;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -109,6 +113,7 @@
};
AppOpsManager mMockAppOps = mock(AppOpsManager.class);
+ ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
MockContext mMockContext = new MockContext() {
@Override
@@ -116,12 +121,22 @@
switch(name) {
case Context.APP_OPS_SERVICE:
return mMockAppOps;
+ case Context.CONNECTIVITY_SERVICE:
+ return mMockConnectivityMgr;
default:
return null;
}
}
@Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (ConnectivityManager.class == serviceClass) {
+ return Context.CONNECTIVITY_SERVICE;
+ }
+ return null;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mMockPkgMgr;
}
@@ -151,6 +166,10 @@
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
private static final int REMOTE_ENCAP_PORT = 4500;
+ private static final String BLESSED_PACKAGE = "blessedPackage";
+ private static final String SYSTEM_PACKAGE = "systemPackage";
+ private static final String BAD_PACKAGE = "badPackage";
+
public IpSecServiceParameterizedTest(
String sourceAddr, String destAddr, String localInnerAddr, int family) {
mSourceAddr = sourceAddr;
@@ -174,15 +193,15 @@
when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
// A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
- .thenReturn(AppOpsManager.MODE_ALLOWED);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
// A system package will not be granted the app op, so this should fall back to
// a permissions check, which should pass.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage")))
- .thenReturn(AppOpsManager.MODE_DEFAULT);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
// A mismatch between the package name and the UID will return MODE_IGNORED.
- when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage")))
- .thenReturn(AppOpsManager.MODE_IGNORED);
+ when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
}
//TODO: Add a test to verify SPI.
@@ -338,7 +357,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -352,7 +371,7 @@
ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -370,14 +389,14 @@
if (mFamily == AF_INET) {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
} else {
try {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
} catch (IllegalArgumentException expected) {
}
@@ -396,14 +415,14 @@
if (mFamily == AF_INET) {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
} else {
try {
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
} catch (IllegalArgumentException expected) {
}
@@ -417,12 +436,12 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
// Attempting to create transform a second time with the same SPIs should throw an error...
try {
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("IpSecService should have thrown an error for reuse of SPI");
} catch (IllegalStateException expected) {
}
@@ -430,7 +449,7 @@
// ... even if the transform is deleted
mIpSecService.deleteTransform(createTransformResp.resourceId);
try {
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
fail("IpSecService should have thrown an error for reuse of SPI");
} catch (IllegalStateException expected) {
}
@@ -443,7 +462,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -467,7 +486,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
mIpSecService.deleteTransform(createTransformResp.resourceId);
verify(mMockNetd, times(1))
@@ -515,7 +534,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
@@ -562,7 +581,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
if (closeSpiBeforeApply) {
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -592,7 +611,7 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
// Close SPI record
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -638,7 +657,7 @@
@Test
public void testCreateTunnelInterface() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
// Check that we have stored the tracking object, and retrieve it
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
@@ -661,11 +680,11 @@
@Test
public void testDeleteTunnelInterface() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
- mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
+ mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
@@ -678,10 +697,73 @@
}
}
+ private Network createFakeUnderlyingNetwork(String interfaceName) {
+ final Network fakeNetwork = new Network(1000);
+ final LinkProperties fakeLp = new LinkProperties();
+ fakeLp.setInterfaceName(interfaceName);
+ when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
+ return fakeNetwork;
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterface() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+
+ final IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+ assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
+ fail("Expected an IllegalArgumentException for invalid resource ID.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+ final IpSecService.UserRecord userRecord =
+ mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+ final TunnelInterfaceRecord tunnelInterfaceInfo =
+ userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+
+ final Network newFakeNetwork =
+ createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException because the underlying network is the"
+ + " network being exposed by this tunnel.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
@Test
public void testTunnelInterfaceBinderDeath() throws Exception {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
@@ -718,9 +800,9 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
if (closeSpiBeforeApply) {
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -728,8 +810,8 @@
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
- mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
- transformResourceId, "blessedPackage");
+ mIpSecService.applyTunnelModeTransform(
+ tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
for (int selAddrFamily : ADDRESS_FAMILIES) {
verify(mMockNetd)
@@ -758,17 +840,17 @@
addAuthAndCryptToIpSecConfig(ipSecConfig);
IpSecTransformResponse createTransformResp =
- mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
// Close SPI record
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
- mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
- transformResourceId, "blessedPackage");
+ mIpSecService.applyTunnelModeTransform(
+ tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
for (int selAddrFamily : ADDRESS_FAMILIES) {
verify(mMockNetd)
@@ -790,7 +872,7 @@
@Test
public void testAddRemoveAddressFromTunnelInterface() throws Exception {
- for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
+ for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
mIpSecService.addAddressToTunnelInterface(
@@ -816,7 +898,7 @@
public void testAddTunnelFailsForBadPackageName() throws Exception {
try {
IpSecTunnelInterfaceResponse createTunnelResp =
- createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage");
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
fail("Expected a SecurityException for badPackage.");
} catch (SecurityException expected) {
}
@@ -830,7 +912,7 @@
try {
String addr = Inet4Address.getLoopbackAddress().getHostAddress();
mIpSecService.createTunnelInterface(
- addr, addr, new Network(0), new Binder(), "blessedPackage");
+ addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
fail("Expected UnsupportedOperationException for disabled feature");
} catch (UnsupportedOperationException expected) {
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index cd4cfcf..46c4081 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -67,6 +67,8 @@
@SmallTest
public class NetworkNotificationManagerTest {
+ private static final String TEST_SSID = "Test SSID";
+ private static final String TEST_EXTRA_INFO = "extra";
static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -76,6 +78,7 @@
WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ WIFI_CAPABILITIES.setSSID(TEST_SSID);
// Set the underyling network to wifi.
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
@@ -129,7 +132,7 @@
when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
- when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
+ when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
@@ -143,11 +146,11 @@
.notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any());
final int transportType = NetworkNotificationManager.approximateTransportType(nai);
if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
- verify(mResources, times(1)).getString(title, eq(any()));
+ verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO));
} else {
verify(mResources, times(1)).getString(title);
}
- verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed);
+ verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed));
}
@Test
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
similarity index 64%
rename from tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
rename to tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index d1d6a26..0f920b3 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -16,8 +16,11 @@
package com.android.framework.permission.tests;
+import android.content.Context;
import android.os.Binder;
-import android.os.IVibratorService;
+import android.os.CombinedVibrationEffect;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -32,27 +35,28 @@
* Verify that Hardware apis cannot be called without required permissions.
*/
@SmallTest
-public class VibratorServicePermissionTest extends TestCase {
+public class VibratorManagerServicePermissionTest extends TestCase {
- private IVibratorService mVibratorService;
+ private IVibratorManagerService mVibratorService;
@Override
protected void setUp() throws Exception {
- mVibratorService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mVibratorService = IVibratorManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
}
/**
- * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions.
+ * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String,
+ * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions.
* <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
+ * {@link android.Manifest.permission#VIBRATE}
*/
public void testVibrate() throws RemoteException {
try {
- final VibrationEffect effect =
- VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
- final VibrationAttributes attrs = new VibrationAttributes.Builder()
+ CombinedVibrationEffect effect =
+ CombinedVibrationEffect.createSynced(
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+ VibrationAttributes attrs = new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_ALARM)
.build();
mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
@@ -64,10 +68,10 @@
}
/**
- * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
+ * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires
+ * permissions.
* <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
+ * {@link android.Manifest.permission#VIBRATE}
*/
public void testCancelVibrate() throws RemoteException {
try {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 8643d8a..16181b6 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -16,12 +16,18 @@
package com.android.server.vcn;
+import static android.net.IpSecManager.IpSecTunnelInterface;
+
+import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import android.net.IpSecManager;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,6 +43,11 @@
public void setUp() throws Exception {
super.setUp();
+ final IpSecTunnelInterface tunnelIface =
+ mContext.getSystemService(IpSecManager.class)
+ .createIpSecTunnelInterface(
+ DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
+ mGatewayConnection.setTunnelInterface(tunnelIface);
mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
mTestLooper.dispatchAll();
}